Komunikacja w czasie rzeczywistym z WebRTC

1. Wprowadzenie

WebRTC to projekt typu open source, który umożliwia komunikację w czasie rzeczywistym za pomocą dźwięku, obrazu i danych w aplikacjach internetowych i natywnych.

WebRTC ma kilka interfejsów API JavaScriptu – kliknij linki, aby zobaczyć wersje demonstracyjne.

Gdzie mogę używać WebRTC?

W przeglądarkach Firefox i Opera oraz w Chrome na komputerze i Androidzie. WebRTC jest też dostępny w aplikacjach natywnych na iOS i Androida.

Co to jest sygnalizacja?

WebRTC używa interfejsu RTCPeerConnection do przesyłania strumieniowych danych między przeglądarkami, ale potrzebuje też mechanizmu do koordynowania komunikacji i wysyłania komunikatów sterujących, czyli sygnalizacji. WebRTC nie określa metod ani protokołów sygnalizacyjnych. W tym samouczku do przesyłania wiadomości użyjesz Socket.IO, ale istnieje wiele alternatyw.

Co to są STUN i TURN?

WebRTC jest zaprojektowany do działania w trybie peer-to-peer, dzięki czemu użytkownicy mogą łączyć się za pomocą najbardziej bezpośredniej trasy. WebRTC jest jednak przystosowany do działania w rzeczywistych warunkach sieciowych: aplikacje klienckie muszą przechodzić przez bramy NAT i zapory sieciowe, a sieć peer-to-peer musi mieć mechanizmy rezerwowe na wypadek, gdyby bezpośrednie połączenie się nie powiodło. W ramach tego procesu interfejsy API WebRTC używają serwerów STUN do uzyskiwania adresu IP komputera i serwerów TURN, które działają jako serwery przekaźnikowe w przypadku niepowodzenia komunikacji peer-to-peer. (Więcej informacji znajdziesz w artykule WebRTC w praktyce).

Czy WebRTC jest bezpieczne?

Szyfrowanie jest obowiązkowe w przypadku wszystkich komponentów WebRTC, a interfejsów API JavaScript można używać tylko w bezpiecznych źródłach (HTTPS lub localhost). Mechanizmy sygnalizacyjne nie są zdefiniowane przez standardy WebRTC, więc musisz zadbać o to, aby używać bezpiecznych protokołów.

2. Przegląd

Utwórz aplikację, która będzie pobierać wideo i robić zdjęcia za pomocą kamery internetowej, a następnie udostępniać je w trybie peer-to-peer za pomocą WebRTC. Przy okazji dowiesz się, jak korzystać z podstawowych interfejsów WebRTC API i skonfigurować serwer do obsługi wiadomości za pomocą Node.js.

Czego się nauczysz

  • Uzyskiwanie obrazu z kamery internetowej
  • Strumieniowanie wideo za pomocą RTCPeerConnection
  • Przesyłanie strumieniowe danych za pomocą RTCDataChannel
  • Konfigurowanie usługi sygnalizacyjnej do wymiany wiadomości
  • Łączenie połączenia równorzędnego i sygnalizacji
  • Zrób zdjęcie i udostępnij je za pomocą kanału danych

Czego potrzebujesz

  • Chrome w wersji 47 lub nowszej
  • Web Server for Chrome lub użyj własnego serwera WWW.
  • Przykładowy kod
  • edytor tekstu,
  • Podstawowa znajomość języków HTML, CSS i JavaScript

3. Pobieranie przykładowego kodu

Pobieranie kodu

Jeśli znasz Git, możesz pobrać kod tego ćwiczenia z GitHuba, klonując go:

git clone https://github.com/googlecodelabs/webrtc-web

Możesz też kliknąć ten przycisk, aby pobrać plik ZIP z kodem:

Otwórz pobrany plik ZIP. Spowoduje to rozpakowanie folderu projektu (adaptive-web-media), który zawiera po 1 folderze dla każdego kroku tego modułu, a także wszystkie potrzebne zasoby.

Całą pracę związaną z kodowaniem wykonasz w katalogu o nazwie work.

Foldery step-nn zawierają gotową wersję każdego kroku tego modułu. Są one podane w celach informacyjnych.

Instalowanie i weryfikowanie serwera WWW

Możesz używać własnego serwera WWW, ale ten przewodnik został zaprojektowany tak, aby dobrze współpracował z serwerem WWW Chrome. Jeśli nie masz jeszcze tej aplikacji, możesz ją zainstalować z Chrome Web Store.

6ddeb4aee53c0f0e.png

Po zainstalowaniu aplikacji Web Server for Chrome kliknij skrót do Aplikacji Chrome na pasku zakładek, na stronie nowej karty lub w Menu z aplikacjami:

1d2b4aa977ab7e24.png

Kliknij ikonę serwera internetowego:

27fce4494f641883.png

Następnie zobaczysz to okno, które umożliwia skonfigurowanie lokalnego serwera WWW:

Screen Shot 2016-02-18 at 11.48.14 AM.png

Kliknij przycisk WYBIERZ FOLDER i wybierz utworzony folder praca. Dzięki temu będziesz mieć możliwość wyświetlania postępów w pracy w Chrome za pomocą adresu URL wyróżnionego w oknie dialogowym serwera internetowego w sekcji Adresy URL serwera internetowego.

W sekcji Opcje zaznacz pole obok opcji Automatycznie wyświetlaj plik index.html, jak pokazano poniżej:

Screen Shot 2016-02-18 at 11.56.30 AM.png

Następnie zatrzymaj i ponownie uruchom serwer, przesuwając przełącznik oznaczony jako Serwer WWW: URUCHOMIONY w lewo, a potem z powrotem w prawo.

Screen Shot 2016-02-18 at 12.22.18 PM.png

Teraz otwórz witrynę w przeglądarce, klikając wyróżniony adres URL serwera internetowego. Powinna Ci się wyświetlić strona podobna do tej, która odpowiada plikowi work/index.html:

18a705cb6ccc5181.png

Oczywiście ta aplikacja nie robi jeszcze niczego ciekawego – na razie jest to tylko minimalny szkielet, którego używamy, aby upewnić się, że serwer WWW działa prawidłowo. Funkcje i elementy układu dodasz w kolejnych krokach.

4. Strumieniowanie wideo z kamery internetowej

Czego się nauczysz

Z tego kroku dowiesz się, jak:

  • Uzyskaj strumień wideo z kamery internetowej.
  • manipulować odtwarzaniem strumienia;
  • Używaj CSS i SVG do manipulowania filmami.

Pełna wersja tego kroku znajduje się w folderze step-01.

Odrobina HTML...

Dodaj element video i element script do pliku index.html w katalogu work:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <video autoplay playsinline></video>

  <script src="js/main.js"></script>

</body>

</html>

...i odrobinę JavaScriptu

Dodaj do pliku main.js w folderze js ten kod:

'use strict';

// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
  video: true,
};

// Video element where stream will be placed.
const localVideo = document.querySelector('video');

// Local stream that will be reproduced on the video.
let localStream;

// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Wypróbuj

Otwórz plik index.html w przeglądarce. Powinien on wyglądać mniej więcej tak (oczywiście z widokiem z kamery internetowej):

9297048e43ed0f3d.png

Jak to działa

Po wywołaniu funkcji getUserMedia() przeglądarka prosi użytkownika o zezwolenie na dostęp do kamery (jeśli po raz pierwszy w przypadku bieżącego pochodzenia żądany jest dostęp do kamery). Jeśli operacja się powiedzie, zwracany jest obiekt MediaStream, który może być używany przez element multimedialny za pomocą atrybutu srcObject:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);


}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Argument constraints pozwala określić, jakie multimedia mają być pobierane. W tym przykładzie tylko wideo, ponieważ dźwięk jest domyślnie wyłączony:

const mediaStreamConstraints = {
  video: true,
};

Możesz używać ograniczeń w przypadku dodatkowych wymagań, takich jak rozdzielczość wideo:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

Specyfikacja MediaTrackConstraints zawiera listę wszystkich potencjalnych typów ograniczeń, ale nie wszystkie opcje są obsługiwane przez wszystkie przeglądarki. Jeśli żądana rozdzielczość nie jest obsługiwana przez aktualnie wybraną kamerę, żądanie getUserMedia() zostanie odrzucone z kodem OverconstrainedError, a użytkownik nie zostanie poproszony o przyznanie uprawnień dostępu do kamery.

Jeśli funkcja getUserMedia() zostanie wykonana prawidłowo, strumień wideo z kamery internetowej zostanie ustawiony jako źródło elementu wideo:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Punkty bonusowe

  • Obiekt localStream przekazywany do getUserMedia() jest w zakresie globalnym, więc możesz go sprawdzić w konsoli przeglądarki: otwórz konsolę, wpisz stream i naciśnij Enter. (Aby wyświetlić konsolę w Chrome, naciśnij Ctrl+Shift+J lub Command+Option+J na Macu).
  • Co zwraca localStream.getVideoTracks()?
  • Spróbuj zadzwonić pod numer localStream.getVideoTracks()[0].stop().
  • Sprawdź obiekt ograniczeń: co się stanie, gdy zmienisz go na {audio: true, video: true}?
  • Jaki rozmiar ma element wideo? Jak za pomocą JavaScriptu uzyskać naturalny rozmiar filmu, a nie rozmiar wyświetlania? Aby to sprawdzić, użyj Narzędzi deweloperskich w Chrome.
  • Spróbuj dodać filtry CSS do elementu wideo. Na przykład:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Spróbuj dodać filtry SVG. Na przykład:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Czego się dowiedziałeś

Z tego kroku dowiesz się, jak:

  • Pobierz wideo z kamery internetowej.
  • Ustaw ograniczenia dotyczące multimediów.
  • Zmień element wideo.

Pełna wersja tego kroku znajduje się w folderze step-01.

Wskazówki

  • Nie zapomnij o atrybucie autoplay w elemencie video. Bez tego zobaczysz tylko jedną klatkę.
  • Istnieje wiele innych opcji ograniczeń getUserMedia(). Obejrzyj prezentację na stronie webrtc.github.io/samples/src/content/peerconnection/constraints. Jak zobaczysz, w tej witrynie znajdziesz wiele interesujących przykładów WebRTC.

Sprawdzona metoda

  • Upewnij się, że element wideo nie wykracza poza kontener. Dodaliśmy atrybuty widthmax-width, aby ustawić preferowany i maksymalny rozmiar filmu. Przeglądarka automatycznie obliczy wysokość:
video {
  max-width: 100%;
  width: 320px;
}

Następny

Masz film, ale jak go transmitować? Dowiesz się tego w następnym kroku.

5. Strumieniowanie wideo za pomocą RTCPeerConnection

Czego się nauczysz

Z tego kroku dowiesz się, jak:

  • Ukryj różnice między przeglądarkami za pomocą warstwy WebRTC, czyli adapter.js.
  • Użyj interfejsu RTCPeerConnection API do przesyłania strumieniowego wideo.
  • sterować nagrywaniem i transmisją multimediów,

Pełna wersja tego kroku znajduje się w folderze step-2.

Co to jest RTCPeerConnection?

RTCPeerConnection to interfejs API do nawiązywania połączeń WebRTC w celu przesyłania strumieniowego wideo i audio oraz wymiany danych.

W tym przykładzie nawiązywane jest połączenie między 2 obiektami RTCPeerConnection (zwanymi węzłami) na tej samej stronie.

Nie ma zbyt wielu praktycznych zastosowań, ale pomaga zrozumieć, jak działa RTCPeerConnection.

Dodawanie elementów wideo i przycisków sterujących

W pliku index.html zastąp pojedynczy element wideo 2 elementami wideo i 3 przyciskami:

<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>


<div>
  <button id="startButton">Start</button>
  <button id="callButton">Call</button>
  <button id="hangupButton">Hang Up</button>
</div>

Jeden element wideo będzie wyświetlać strumień z getUserMedia(), a drugi – ten sam film przesyłany strumieniowo za pomocą RTCPeerconnection. (W rzeczywistości jeden element wideo wyświetlałby strumień lokalny, a drugi – strumień zdalny).

Dodaj plik shim adapter.js

Dodaj link do bieżącej wersji pliku adapter.js nad linkiem do pliku main.js:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Plik Index.html powinien teraz wyglądać tak:

<!DOCTYPE html>
<html>

<head>
  <title>Realtime communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Realtime communication with WebRTC</h1>

  <video id="localVideo" autoplay playsinline></video>
  <video id="remoteVideo" autoplay playsinline></video>

  <div>
    <button id="startButton">Start</button>
    <button id="callButton">Call</button>
    <button id="hangupButton">Hang Up</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

Instalowanie kodu RTCPeerConnection

Zastąp plik main.js wersją z folderu step-02.

Zadzwoń

Otwórz plik index.html, kliknij przycisk Start, aby uzyskać obraz z kamery internetowej, a następnie kliknij Call, aby nawiązać połączenie peer-to-peer. W obu elementach wideo powinien być widoczny ten sam film (z kamery internetowej). Aby wyświetlić logowanie WebRTC, otwórz konsolę przeglądarki.

Jak to działa

Ten krok wykonuje wiele czynności…

WebRTC używa interfejsu RTCPeerConnection API do nawiązywania połączenia w celu przesyłania strumieniowego wideo między klientami WebRTC, zwanymi peerami.

W tym przykładzie 2 obiekty RTCPeerConnection znajdują się na tej samej stronie: pc1pc2. Nie ma zbyt wielu praktycznych zastosowań, ale dobrze pokazuje, jak działają interfejsy API.

Konfigurowanie połączenia między elementami równorzędnymi WebRTC obejmuje 3 zadania:

  • Utwórz obiekt RTCPeerConnection dla każdego końca połączenia i na każdym końcu dodaj lokalny strumień z getUserMedia().
  • Pobieranie i udostępnianie informacji o sieci: potencjalne punkty końcowe połączenia są znane jako kandydaci ICE.
  • Pobieranie i udostępnianie opisów lokalnych i zdalnych: metadane o lokalnych mediach w formacie SDP.

Wyobraź sobie, że Alicja i Bob chcą użyć RTCPeerConnection do skonfigurowania czatu wideo.

Najpierw Alicja i Robert wymieniają się informacjami o sieci. Wyrażenie „znajdowanie kandydatów” odnosi się do procesu znajdowania interfejsów sieciowych i portów za pomocą platformy ICE.

  1. Alicja tworzy obiekt RTCPeerConnection z procedurą obsługi onicecandidate (addEventListener('icecandidate')). Odpowiada to temu kodowi z pliku main.js:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice dzwoni pod numer getUserMedia() i dodaje przekazany strumień:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. Wywoływany jest moduł obsługi onicecandidate z kroku 1, gdy staną się dostępne kandydaci do sieci.
  2. Alicja wysyła do Boba zserializowane dane kandydatów. W prawdziwej aplikacji ten proces (zwany sygnalizacją) odbywa się za pomocą usługi przesyłania wiadomości. Dowiesz się, jak to zrobić, w dalszej części. Oczywiście na tym etapie oba obiekty RTCPeerConnection znajdują się na tej samej stronie i mogą komunikować się bezpośrednio bez potrzeby korzystania z zewnętrznych wiadomości.
  3. Gdy Bob otrzyma od Alicji wiadomość z kandydatem, wywoła funkcję addIceCandidate(), aby dodać kandydata do opisu zdalnego elementu równorzędnego:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

Węzły WebRTC muszą też wykrywać i wymieniać informacje o lokalnych i zdalnych multimediach audio i wideo, takie jak rozdzielczość i możliwości kodeka. Sygnalizacja służąca do wymiany informacji o konfiguracji multimediów polega na wymianie bloków metadanych, zwanych ofertą i odpowiedzią, w formacie protokołu opisu sesji, znanym jako SDP:

  1. Alicja uruchamia metodę createOffer() RTCPeerConnection. Zwrócony obiekt Promise zawiera RTCSessionDescription: lokalny opis sesji Alicji:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. Jeśli się to uda, Alicja ustawi opis lokalny za pomocą setLocalDescription(), a następnie wyśle ten opis sesji do Boba za pomocą kanału sygnalizacyjnego.
  2. Robert ustawia przesłany przez Alicję opis jako opis zdalny za pomocą setRemoteDescription().
  3. Bob uruchamia metodę RTCPeerConnection createAnswer(), przekazując jej zdalny opis, który otrzymał od Alicji, aby można było wygenerować lokalną sesję zgodną z sesją Alicji. Obietnica createAnswer() przekazuje RTCSessionDescription: Robert ustawia go jako opis lokalny i wysyła do Alicji.
  4. Gdy Alicja otrzyma opis sesji Roberta, ustawi go jako opis zdalny za pomocą setRemoteDescription().
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}
  1. Ping!

Punkty bonusowe

  1. Otwórz stronę chrome://webrtc-internals. Zawiera statystyki WebRTC i dane do debugowania. (Pełna lista adresów URL Chrome znajduje się na stronie chrome://about).
  2. Nadaj stronie styl za pomocą CSS:
  • Umieść filmy obok siebie.
  • Ustaw przyciski o tej samej szerokości i większym tekście.
  • Sprawdź, czy układ działa na urządzeniach mobilnych.
  1. W konsoli narzędzi deweloperskich w Chrome sprawdź localStream, localPeerConnectionremotePeerConnection.
  2. W konsoli sprawdź localPeerConnectionpc1.localDescription. Jak wygląda format SDP?

Czego się dowiedziałeś

Z tego kroku dowiesz się, jak:

  • Ukryj różnice między przeglądarkami za pomocą warstwy WebRTC, czyli adapter.js.
  • Użyj interfejsu RTCPeerConnection API do przesyłania strumieniowego wideo.
  • sterować nagrywaniem i transmisją multimediów,
  • Udostępnianie informacji o mediach i sieci między urządzeniami, aby umożliwić połączenie WebRTC.

Pełna wersja tego kroku znajduje się w folderze step-2.

Wskazówki

  • Na tym etapie jest wiele do nauczenia się. Więcej informacji o RTCPeerConnection znajdziesz na stronie webrtc.org. Na tej stronie znajdziesz sugestie dotyczące platform JavaScriptu, jeśli chcesz używać WebRTC, ale nie chcesz korzystać z interfejsów API.
  • Więcej informacji o wypełniaczu adapter.js znajdziesz w repozytorium adapter.js na GitHubie.
  • Chcesz zobaczyć, jak wygląda najlepsza na świecie aplikacja do czatu wideo? Zapoznaj się z AppRTC, czyli kanoniczną aplikacją projektu WebRTC do połączeń WebRTC: aplikacja, kod. Czas konfiguracji połączenia jest krótszy niż 500 ms.

Sprawdzona metoda

  • Aby zabezpieczyć kod na przyszłość, używaj nowych interfejsów API opartych na obietnicach i włączaj zgodność z przeglądarkami, które ich nie obsługują, za pomocą adapter.js.

Następny

W tym kroku pokazujemy, jak używać WebRTC do przesyłania strumieniowego wideo między urządzeniami, ale ten przewodnik dotyczy też danych.

W następnym kroku dowiesz się, jak przesyłać dowolne dane za pomocą RTCDataChannel.

6. Używanie RTCDataChannel do wymiany danych

Czego się nauczysz

  • Jak wymieniać dane między punktami końcowymi WebRTC (peerami).

Pełna wersja tego kroku znajduje się w folderze step-03.

Aktualizowanie kodu HTML

W tym kroku użyjesz kanałów danych WebRTC do wysyłania tekstu między 2 elementami textarea na tej samej stronie. Nie jest to zbyt przydatne, ale pokazuje, jak WebRTC może być używany do udostępniania danych, a także strumieniowego przesyłania filmów.

Usuń elementy wideo i przycisku z pliku index.html i zastąp je tym kodem HTML:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

W jednym polu tekstowym będzie można wpisywać tekst, a w drugim będzie on wyświetlany w miarę przesyłania między użytkownikami.

Plik index.html powinien teraz wyglądać tak:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
  <textarea id="dataChannelReceive" disabled></textarea>

  <div id="buttons">
    <button id="startButton">Start</button>
    <button id="sendButton">Send</button>
    <button id="closeButton">Stop</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Aktualizowanie kodu JavaScript

Zastąp zawartość pliku main.js zawartością pliku step-03/js/main.js.

Wypróbuj przesyłanie strumieniowe danych między urządzeniami: otwórz index.html, kliknij Start, aby skonfigurować połączenie między urządzeniami, wpisz tekst w textarea po lewej stronie, a następnie kliknij Send, aby przesłać tekst za pomocą kanałów danych WebRTC.

Jak to działa

Ten kod używa RTCPeerConnection i RTCDataChannel, aby umożliwić wymianę wiadomości tekstowych.

Większość kodu w tym kroku jest taka sama jak w przykładzie RTCPeerConnection.

Większość nowego kodu znajduje się w funkcjach sendData()createConnection():

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

Składnia RTCDataChannel jest celowo podobna do WebSocket, z metodą send() i zdarzeniem message.

Zwróć uwagę na użycie dataConstraint. Kanały danych można skonfigurować tak, aby włączać różne typy udostępniania danych, np. priorytetowe traktowanie niezawodnego dostarczania danych zamiast wydajności. Więcej informacji o opcjach znajdziesz w Mozilla Developer Network.

Punkty bonusowe

  1. W przypadku protokołu SCTP, który jest używany przez kanały danych WebRTC, niezawodne i uporządkowane dostarczanie danych jest domyślnie włączone. Kiedy RTCDataChannel może potrzebować niezawodnego dostarczania danych, a kiedy wydajność może być ważniejsza – nawet jeśli oznacza to utratę niektórych danych?
  2. Użyj CSS, aby ulepszyć układ strony, i dodaj atrybut zastępczy do pola tekstowego „dataChannelReceive”.
  3. Przetestuj stronę na urządzeniu mobilnym.

Czego się dowiedziałeś

Z tego kroku dowiesz się, jak:

  • nawiązywać połączenia między 2 elementami równorzędnymi WebRTC,
  • wymieniać dane tekstowe między urządzeniami.

Pełna wersja tego kroku znajduje się w folderze step-03.

Więcej informacji

Następny

Wiesz już, jak wymieniać dane między użytkownikami na tej samej stronie, ale jak to zrobić między różnymi komputerami? Najpierw musisz skonfigurować kanał sygnalizacyjny do wymiany wiadomości z metadanymi. W następnym kroku dowiesz się, jak to zrobić.

7. Konfigurowanie usługi sygnalizacyjnej do wymiany wiadomości

Czego się nauczysz

Z tego kroku dowiesz się, jak:

  • Użyj npm, aby zainstalować zależności projektu określone w pliku package.json.
  • Uruchom serwer Node.js i użyj node-static do obsługi plików statycznych.
  • Skonfiguruj usługę przesyłania wiadomości w Node.js za pomocą Socket.IO.
  • Używaj ich do tworzenia „pokoi” i wymiany wiadomości.

Pełna wersja tego kroku znajduje się w folderze step-04.

Pojęcia

Aby skonfigurować i utrzymać połączenie WebRTC, klienci WebRTC (węzły) muszą wymieniać metadane:

  • Informacje o kandydacie (sieci).
  • Wiadomości offeranswer zawierające informacje o mediach, takie jak rozdzielczość i kodeki.

Innymi słowy, przed rozpoczęciem strumieniowania peer-to-peer dźwięku, obrazu lub danych wymagana jest wymiana metadanych. Ten proces nazywa się sygnalizowaniem.

W poprzednich krokach obiekty RTCPeerConnection nadawcy i odbiorcy znajdują się na tej samej stronie, więc „sygnalizacja” polega po prostu na przekazywaniu metadanych między obiektami.

W rzeczywistej aplikacji obiekty RTCPeerConnection nadawcy i odbiorcy działają na stronach internetowych na różnych urządzeniach, więc potrzebujesz sposobu na przekazywanie między nimi metadanych.

W tym celu używasz serwera sygnalizacyjnego, czyli serwera, który może przekazywać wiadomości między klientami WebRTC (peerami). Rzeczywiste wiadomości to zwykły tekst: ciągi znaków będące obiektami JavaScript.

Wymaganie wstępne: zainstaluj Node.js

Aby wykonać kolejne kroki tego laboratorium (foldery step-04step-06), musisz uruchomić serwer na hoście lokalnym za pomocą Node.js.

Możesz pobrać i zainstalować Node.js, klikając ten link lub korzystając z wybranego menedżera pakietów.

Po zainstalowaniu będziesz mieć możliwość zaimportowania zależności wymaganych w kolejnych krokach (uruchomienie npm install) oraz uruchomienia małego serwera localhost, aby wykonać ćwiczenia (uruchomienie node index.js). Te polecenia zostaną podane później, gdy będą potrzebne.

Informacje o aplikacji

WebRTC korzysta z interfejsu API JavaScriptu po stronie klienta, ale w praktyce wymaga też serwera sygnalizacyjnego (do przesyłania wiadomości) oraz serwerów STUN i TURN. Więcej informacji znajdziesz tutaj.

W tym kroku utworzysz prosty serwer sygnalizacyjny Node.js, używając modułu Node.js Socket.IO i biblioteki JavaScript do przesyłania wiadomości. Znajomość Node.js i Socket.IO będzie przydatna, ale nie jest kluczowa, ponieważ komponenty do przesyłania wiadomości są bardzo proste.

W tym przykładzie serwer (aplikacja Node.js) jest zaimplementowany w pliku index.js, a klient, który na nim działa (aplikacja internetowa), jest zaimplementowany w pliku index.html.

Aplikacja Node.js w tym kroku ma 2 zadania.

Po pierwsze, działa jako przekaźnik wiadomości:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

Po drugie, zarządza „pokojami” czatu wideo WebRTC:

if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

Nasza prosta aplikacja WebRTC umożliwi maksymalnie 2 osobom udostępnianie pokoju.

HTML i JavaScript

Zaktualizuj plik index.html, aby wyglądał tak:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Na tym etapie na stronie nic nie zobaczysz: wszystkie logi są zapisywane w konsoli przeglądarki. (Aby wyświetlić konsolę w Chrome, naciśnij Ctrl+Shift+J lub Command+Option+J na Macu).

Zastąp js/main.js tym kodem:

'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

Konfigurowanie Socket.IO do działania w Node.js

W pliku HTML możesz zauważyć, że używasz pliku Socket.IO:

<script src="/socket.io/socket.io.js"></script>

W katalogu najwyższego poziomu work utwórz plik o nazwie package.json z tą zawartością:

{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Jest to manifest aplikacji, który informuje menedżera pakietów Node (npm), jakie zależności projektu należy zainstalować.

Aby zainstalować zależności (np. /socket.io/socket.io.js), uruchom w terminalu wiersza poleceń w katalogu roboczym to polecenie:

npm install

Powinien pojawić się dziennik instalacji, który kończy się w ten sposób:

3ab06b7bcc7664b9.png

Jak widać, npm zainstalował zależności zdefiniowane w pliku package.json.

Utwórz nowy plik index.js w katalogu najwyższego poziomu work (nie w katalogu js) i dodaj ten kod:

'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});

W terminalu wiersza poleceń uruchom to polecenie w katalogu work:

node index.js

W przeglądarce otwórz adres localhost:8080.

Za każdym razem, gdy otworzysz ten adres URL, pojawi się prośba o wpisanie nazwy pokoju. Aby dołączyć do tego samego pokoju, za każdym razem wybieraj tę samą nazwę, np. „foo”.

Otwórz stronę nowej karty i ponownie otwórz localhost:8080. Wybierz tę samą nazwę pokoju.

Otwórz adres localhost:8080 w trzeciej karcie lub trzecim oknie. Ponownie wybierz tę samą nazwę pokoju.

Sprawdź konsolę na każdej z kart. Powinny się w niej pojawić logi z powyższego kodu JavaScript.

Punkty bonusowe

  1. Jakie alternatywne mechanizmy przesyłania wiadomości mogą być możliwe? Jakie problemy mogą wystąpić podczas korzystania z „czystego” protokołu WebSocket?
  2. Jakie problemy mogą wystąpić podczas skalowania tej aplikacji? Czy możesz opracować metodę testowania tysięcy lub milionów jednoczesnych żądań dotyczących pokoi?
  3. Ta aplikacja używa prompta JavaScriptu, aby uzyskać nazwę pokoju. Znajdź sposób na uzyskanie nazwy pokoju z adresu URL. Na przykład localhost:8080/foo dałoby nazwę pokoju foo.

Czego się dowiedziałeś

Z tego kroku dowiedziałeś(-aś) się, jak:

  • Użyj npm, aby zainstalować zależności projektu określone w pliku package.json.
  • Uruchom serwer Node.js, aby obsługiwać pliki statyczne.
  • Skonfiguruj usługę przesyłania wiadomości w Node.js za pomocą socket.io.
  • Używaj ich do tworzenia „pokoi” i wymiany wiadomości.

Pełna wersja tego kroku znajduje się w folderze step-04.

Więcej informacji

Następny

Dowiedz się, jak używać sygnalizacji, aby umożliwić 2 użytkownikom nawiązanie połączenia peer-to-peer.

8. Łączenie połączenia równorzędnego i sygnalizacji

Czego się nauczysz

Z tego kroku dowiesz się, jak:

  • Uruchamianie usługi sygnalizacyjnej WebRTC za pomocą Socket.IO działającej w Node.js
  • Użyj tej usługi do wymiany metadanych WebRTC między urządzeniami.

Pełna wersja tego kroku znajduje się w folderze step-05.

Zastępowanie kodu HTML i JavaScript

Zastąp zawartość pliku index.html tym kodem:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <div id="videos">
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Zastąp js/main.js zawartością pliku step-05/js/main.js.

Uruchamianie serwera Node.js

Jeśli nie korzystasz z tego przewodnika w katalogu work, może być konieczne zainstalowanie zależności dla folderu step-05 lub bieżącego folderu roboczego. W katalogu roboczym uruchom to polecenie:

npm install

Jeśli po zainstalowaniu serwer Node.js nie działa, uruchom go, wywołując to polecenie w katalogu work:

node index.js

Upewnij się, że używasz wersji pliku index.js z poprzedniego kroku, która implementuje Socket.IO. Więcej informacji o Node i Socket IO znajdziesz w sekcji „Konfigurowanie usługi sygnalizacyjnej do wymiany wiadomości”.

W przeglądarce otwórz adres localhost:8080.

Otwórz ponownie stronę localhost:8080 w nowej karcie lub nowym oknie. Jeden element wideo będzie wyświetlać lokalny strumień z getUserMedia(), a drugi – „zdalny” film przesyłany strumieniowo przez RTCPeerconnection.

Wyświetl logowanie w konsoli przeglądarki.

Punkty bonusowe

  1. Ta aplikacja obsługuje tylko czaty wideo 1:1. Jak możesz zmienić projekt, aby umożliwić więcej niż jednej osobie udostępnianie tego samego pokoju czatu wideo?
  2. W przykładzie nazwa pokoju foo jest zakodowana na stałe. Jak najlepiej włączyć inne nazwy pokoi?
  3. W jaki sposób użytkownicy będą udostępniać nazwę pokoju? Spróbuj znaleźć alternatywę dla udostępniania nazw pokoi.
  4. Jak można zmienić aplikację

Czego się dowiedziałeś

Z tego kroku dowiesz się, jak:

  • Uruchom usługę sygnalizacyjną WebRTC za pomocą Socket.IO działającego w Node.js.
  • Użyj tej usługi do wymiany metadanych WebRTC między urządzeniami.

Pełna wersja tego kroku znajduje się w folderze step-05.

Wskazówki

  • Statystyki WebRTC i dane debugowania są dostępne na stronie chrome://webrtc-internals.
  • Strona test.webrtc.org umożliwia sprawdzenie środowiska lokalnego oraz przetestowanie kamery i mikrofonu.
  • Jeśli masz problemy z pamięcią podręczną, wykonaj te czynności:
  • Wymuś odświeżenie, przytrzymując Ctrl i klikając przycisk Odśwież.
  • Ponowne uruchamianie przeglądarki
  • Uruchom npm cache clean z wiersza poleceń.

Następny

Dowiedz się, jak zrobić zdjęcie, uzyskać dane obrazu i udostępnić je zdalnym użytkownikom.

9. Zrób zdjęcie i udostępnij je za pomocą kanału danych

Czego się nauczysz

Z tego kroku dowiesz się, jak:

  • Zrób zdjęcie i pobierz z niego dane za pomocą elementu canvas.
  • Wymieniaj dane obrazu ze zdalnym użytkownikiem.

Pełna wersja tego kroku znajduje się w folderze step-06.

Jak to działa

Wcześniej dowiedzieliśmy się, jak wymieniać wiadomości tekstowe za pomocą RTCDataChannel.

Ten krok umożliwia udostępnianie całych plików, w tym przypadku zdjęć zrobionych za pomocą getUserMedia().

Główne elementy tego kroku to:

  1. Ustanów kanał danych. Pamiętaj, że na tym etapie nie dodajesz do połączenia między urządzeniami żadnych strumieni multimedialnych.
  2. Przechwyć strumień wideo z kamery internetowej użytkownika za pomocą getUserMedia():
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. Gdy użytkownik kliknie przycisk Zrób zdjęcie, pobierz klatkę z transmisji wideo i wyświetl ją w elemencie canvas:
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. Gdy użytkownik kliknie przycisk Wyślij, przekonwertuj obraz na bajty i wyślij je za pomocą kanału danych:
function sendPhoto() {
  // Split data channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

  console.log('Sending a total of ' + len + ' byte(s)');
  dataChannel.send(len);

  // split the photo and send in chunks of about 64KB
  for (var i = 0; i < n; i++) {
    var start = i * CHUNK_LEN,
      end = (i + 1) * CHUNK_LEN;
    console.log(start + ' - ' + (end - 1));
    dataChannel.send(img.data.subarray(start, end));
  }

  // send the reminder, if any
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}
  1. Strona odbierająca przekształca bajty wiadomości z kanału danych z powrotem w obraz i wyświetla go użytkownikowi:
function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // trail is the element holding the incoming images
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

Pobierz kod

Zastąp zawartość folderu work zawartością folderu step-06. Plik index.html w folderze work powinien teraz wyglądać tak:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Jeśli nie korzystasz z tego przewodnika w katalogu work, może być konieczne zainstalowanie zależności dla folderu step-06 lub bieżącego folderu roboczego. Wystarczy uruchomić to polecenie w katalogu roboczym:

npm install

Jeśli po zainstalowaniu serwer Node.js nie działa, uruchom go, wywołując to polecenie z katalogu work:

node index.js

Upewnij się, że używasz wersji pliku index.js, która implementuje Socket.IO. Jeśli wprowadzisz zmiany, pamiętaj o ponownym uruchomieniu serwera Node.js. Więcej informacji o Node i Socket IO znajdziesz w sekcji „Konfigurowanie usługi sygnalizacyjnej do wymiany wiadomości”.

W razie potrzeby kliknij przycisk Zezwól, aby zezwolić aplikacji na korzystanie z kamery internetowej.

Aplikacja utworzy losowy identyfikator pokoju i doda go do adresu URL. Otwórz adres URL z paska adresu w nowej karcie lub nowym oknie przeglądarki.

Kliknij przycisk Zrób zdjęcie i wyślij, a potem sprawdź obszar Przychodzące na drugiej karcie u dołu strony. Aplikacja przenosi zdjęcia między kartami.

Powinien pojawić się ekran podobny do tego:

911b40f36ba6ba8.png

Punkty bonusowe

  1. Jak zmienić kod, aby można było udostępniać pliki dowolnego typu?

Więcej informacji

Czego się dowiedziałeś

  • Jak zrobić zdjęcie i pobrać z niego dane za pomocą elementu canvas.
  • Jak wymieniać te dane z użytkownikiem zdalnym.

Pełna wersja tego kroku znajduje się w folderze step-06.

10. Gratulacje

Udało Ci się utworzyć aplikację do strumieniowania wideo i wymiany danych w czasie rzeczywistym.

Czego się dowiedziałeś

Z tego modułu dowiedzieliśmy się, jak:

  • Pobierz wideo z kamery internetowej.
  • Strumieniowanie wideo za pomocą RTCPeerConnection.
  • Przesyłanie strumieniowe danych za pomocą RTCDataChannel.
  • Skonfiguruj usługę sygnalizacyjną do wymiany wiadomości.
  • Połącz połączenie równorzędne i sygnalizację.
  • Zrób zdjęcie i udostępnij je za pomocą kanału danych.

Dalsze kroki

Więcej informacji

  • Wiele materiałów na temat rozpoczynania pracy z WebRTC znajdziesz na stronie webrtc.org.