Pierwsze kroki z połączeniami równorzędnymi

Połączenia peer to część specyfikacji WebRTC, która dotyczy łączenia dwóch aplikacji na różnych komputerach w celu komunikowania się przy użyciu protokołu peer-to-peer. Komunikacja między użytkownikami może odbywać się za pomocą wideo, audio lub dowolnych danych binarnych (w przypadku klientów obsługujących interfejs RTCDataChannel API). Aby dowiedzieć się, jak dwa połączenia równorzędne mogą się połączyć, obaj klienci muszą udostępnić konfigurację serwera ICE. Jest to STUN lub serwer TURN Jest to często sygnał sygnalizacyjny.

Sygnalizacja

Specyfikacja WebRTC zawiera interfejsy API do komunikowania się z serwerem ICE (Internet Connectivity Instytucja), ale komponent sygnalizacyjny nie jest częścią tego interfejsu. Sygnały są niezbędne, by dwóch partnerów mogło się dowiedzieć, w jaki sposób powinni się połączyć. Zwykle te problemy rozwiązuje zwykły interfejs API oparty na HTTP (np. usługa REST lub inny mechanizm RPC), w którym aplikacje internetowe mogą przekazywać niezbędne informacje, zanim rozpocznie się połączenie równorzędne.

Fragment kodu poniżej pokazuje, w jaki sposób ta fikcyjna usługa do sygnalizowania może służyć do wysyłania i odbierania wiadomości asynchronicznie. Będzie ona potrzebna w pozostałych przykładach tego przewodnika.

// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');

Sygnalizowanie można wdrożyć na wiele różnych sposobów. Specyfikacja WebRTC nie preferuje żadnego konkretnego rozwiązania.

Inicjowanie połączeń równorzędnych

Każde połączenie równorzędne jest obsługiwane przez obiekt RTCPeerConnection. Konstruktor tej klasy przyjmuje jako pojedynczy obiekt RTCConfiguration obiekt. Ten obiekt określa sposób konfiguracji połączenia równorzędnego i powinien zawierać informacje o używanych serwerach ICE.

Po utworzeniu RTCPeerConnection musimy utworzyć ofertę SDP lub odpowiedź, w zależności od tego, czy dzwonimy, czy odbieramy połączenie. Po utworzeniu oferty lub odpowiedzi SDP należy ją wysłać do zdalnego peera przez inny kanał. Przekazywanie obiektów SDP do zdalnych równorzędnych połączeń równorzędnych jest nazywane sygnałami i nie jest objęte specyfikacją WebRTC.

Aby zainicjować konfigurację połączenia równorzędnego po stronie wywołania, tworzy obiekt RTCPeerConnection, a następnie wywołujemy obiekt createOffer(), by utworzyć obiekt RTCSessionDescription. Ten opis sesji jest ustawiany jako lokalny opis za pomocą setLocalDescription(), a następnie wysyłany przez nasz kanał sygnałowy do odbiorcy. Ponadto tworzymy detektor dla naszego kanału sygnalizacyjnego, na którym odbierane są odpowiedzi na proponowany opis sesji po stronie odbiorcy.

async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}

Po stronie odbiorcy czekamy na pojawienie się oferty przed utworzeniem instancji RTCPeerConnection. Gdy to zrobisz, ustawimy otrzymaną ofertę za pomocą setRemoteDescription(). Następnie łączy się z createAnswer(), aby utworzyć odpowiedź na otrzymaną ofertę. Odpowiedź jest ustawiana jako opis lokalny przy użyciu setLocalDescription(), a następnie wysyłana do serwera połączeń przez nasz serwer sygnalizacji.

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

Po wyznaczeniu przez obie osoby opisów zarówno sesji lokalnej, jak i zdalnej, zna umiejętności oferowane w grupie zdalnej. Nie oznacza to, że połączenie z innymi aplikacjami jest gotowe. Aby to osiągnąć, musimy zebrać kandydatów ICE na każdym peerze i przesłać (za pomocą kanału sygnalizacyjnego) do drugiej grupy.

Kandydaci do ICE

Zanim 2 osoby będą mogły łączyć się przez WebRTC, muszą wymienić się informacjami o połączeniach. Sytuacja sieciowa może być różna w zależności od wielu czynników, dlatego zwykle do wykrywania potencjalnych kandydatów do połączenia z peerem używana jest usługa zewnętrzna. Ta usługa nazywa się ICE i używa serwera STUN lub TURN. STUN to skrót od sesji sesji NAT-s (Nawigacja sesji) dla NAT. Jest on używany pośrednio w większości aplikacji WebRTC.

TURN (Traversal Using Relay NAT) to bardziej zaawansowane rozwiązanie, które obejmuje protokoły STUN i większość komercyjnych usług WebRTC korzysta z serwera TURN do nawiązywania połączeń między peerami. Interfejs API WebRTC obsługuje zarówno STUN, jak i TURN i jest gromadzony w ramach pełniejszego cyklu połączeń internetowych. Podczas tworzenia połączenia WebRTC zwykle dostarczamy co najmniej 1 serwer ICE w konfiguracji obiektu RTCPeerConnection.

Trick Trice

Po utworzeniu obiektu RTCPeerConnection bazowa platforma używa dostępnych serwerów ICE do gromadzenia kandydatów do utworzenia łączności (kandydatów ICE). Zdarzenie icegatheringstatechange na RTCPeerConnection wskazuje, jak wygląda zjazd ICE (new, gathering lub complete).

Choć jest możliwe, że peer rozpocznie oczekiwanie na zakończenie zgromadzenia ICE, zazwyczaj lepiej jest zastosować technikę &trickingu lodu, a następnie przesyłać każdego kandydata do ICE po odkryciu. To znacznie zmniejszy czas konfiguracji połączenia peer-to-peer i pozwoli rozpocząć rozmowę wideo z mniejszym opóźnieniem.

Aby zebrać kandydatów ICE, dodaj detektor zdarzeń icecandidate. RTCPeerConnectionIceEvent wyemitowany w tym detektorze będzie zawierał właściwość candidate, która reprezentuje nowego kandydata, który należy wysłać do zdalnego peera (zobacz Signinging).

// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});

Połączenie zostało nawiązane

Po otrzymaniu kandydatów ICE należy spodziewać się, że stan naszego połączenia równorzędnego zmieni się w stan połączenia. Aby tego uniknąć, dodajemy do listy RTCPeerConnection listę odbiorców nasłuchującą zdarzeń connectionstatechange.

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

Dokumentacja RTCPeerConnection API