1. Wprowadzenie
WebRTC to projekt open source umożliwiający w czasie rzeczywistym komunikację audio, wideo i danych w aplikacjach internetowych i natywnych.
WebRTC ma kilka interfejsów API JavaScript. Kliknij linki, aby zobaczyć wersje demonstracyjne.
getUserMedia()
: rejestruj dźwięk i obraz.MediaRecorder
: nagrywanie dźwięku i obrazu.RTCPeerConnection
: strumieniowanie dźwięku i obrazu między użytkownikami.RTCDataChannel
: przesyłanie strumieniowe danych między użytkownikami.
Gdzie mogę używać WebRTC?
w Firefoksie, Operze i Chrome na komputerze oraz w Androidzie. WebRTC jest też dostępny w przypadku aplikacji natywnych na iOS i Androida.
Co to jest sygnalizowanie?
WebRTC używa protokołu RTCPeerConnection do przesyłania strumieniowego danych między przeglądarkami, ale potrzebuje również mechanizmu koordynowania komunikacji i wysyłania komunikatów sterujących. Jest to tzw. sygnalizacja. Metody i protokoły sygnałów nie są określane przez WebRTC. W tym ćwiczeniu w Codelabs będziesz przesyłać wiadomości przy użyciu interfejsu Socket.IO, ale istnieje wiele alternatywnych rozwiązań.
Co to są STUN i TURN?
WebRTC został zaprojektowany do pracy typu peer-to-peer, więc użytkownicy mogą łączyć się jak najbardziej bezpośrednią trasą. WebRTC został jednak opracowany tak, aby radził sobie z siecią w świecie rzeczywistym: aplikacje klienckie muszą omijać bramy NAT i zapory sieciowe, a sieć równorzędna wymaga obsługi w przypadku problemów w przypadku awarii połączenia bezpośredniego. W ramach tego procesu interfejsy API WebRTC korzystają z serwerów STUN, aby uzyskać adres IP Twojego komputera, a serwery TURN mają działać jako serwery przekazujące w przypadku problemów z komunikacją peer-to-peer. (Więcej informacji znajdziesz w artykule WebRTC w świecie rzeczywistym).
Czy WebRTC jest bezpieczny?
Szyfrowanie jest obowiązkowe dla wszystkich komponentów WebRTC, a interfejsów API JavaScriptu można używać tylko z bezpiecznych źródeł (HTTPS lub localhost). Mechanizmy sygnalizowania nie są zdefiniowane przez standardy WebRTC, więc należy zadbać o to, aby używać bezpiecznych protokołów.
2. Omówienie
Stwórz aplikację do pobierania filmów i robienia zrzutów za pomocą kamery internetowej oraz udostępniania ich peer-to-peer przez WebRTC. Dowiesz się, jak korzystać z podstawowych interfejsów API WebRTC i skonfigurować serwer do obsługi wiadomości za pomocą Node.js.
Czego się nauczysz
- Nagrywaj obraz z kamery internetowej
- Strumieniowe przesyłanie wideo przy użyciu RTCPeerConnection
- Strumieniowanie 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 przez kanał danych
Czego potrzebujesz
- Chrome 47 lub nowsza,
- Serwer WWW dla Chrome lub użyj własnego serwera WWW.
- Przykładowy kod
- Edytor tekstu
- Podstawowa znajomość języka HTML, CSS i JavaScript
3. Pobieranie przykładowego kodu
Pobieranie kodu
Jeśli znasz git, możesz pobrać kod tego ćwiczenia z GitHub, sklonują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) zawierającego po 1 folderze na każdy krok tego ćwiczenia w Codelabs wraz ze wszystkimi potrzebnymi zasobami.
Cały proces kodowania będzie odbywać się w katalogu work.
Foldery step-nn zawierają gotową wersję każdego kroku tego ćwiczenia w Codelabs. Są to informacje w celach informacyjnych.
Zainstaluj i zweryfikuj serwer WWW
Możesz wykorzystać własny serwer WWW, ale to ćwiczenie w Codelabs działa dobrze z serwerem Chrome Web Server. Jeśli nie masz jeszcze zainstalowanej tej aplikacji, możesz ją zainstalować ze sklepu Chrome Web Store.
Po zainstalowaniu aplikacji Serwer internetowy dla Chrome kliknij skrót do aplikacji Chrome na pasku zakładek, na stronie Nowa karta lub w Menu z aplikacjami:
Kliknij ikonę Serwer WWW:
Zostanie wyświetlone okno dialogowe, w którym możesz skonfigurować lokalny serwer WWW:
Kliknij przycisk WYBIERZ FOLDER i wybierz utworzony przed chwilą folder praca. Umożliwi Ci to przeglądanie postępów w Chrome pod adresem URL wyróżnionym w oknie Serwer WWW w sekcji Adresy URL serwera WWW.
W sekcji Opcje zaznacz pole wyboru Automatycznie pokazuj plik index.html w następujący sposób:
Następnie zatrzymaj i ponownie uruchom serwer, przesuwając przełącznik Serwer WWW: STARTED w lewo, a następnie z powrotem w prawo.
Następnie otwórz w przeglądarce witrynę służbową, klikając zaznaczony adres URL serwera WWW. Zobaczysz stronę podobną do tej: work/index.html:
Oczywiście ta aplikacja jeszcze nie robi nic interesującego – na razie jej rozmiar to tylko minimalny szkielet, którego używamy do zapewnienia poprawnego działania Twojego serwera WWW. W kolejnych krokach dodasz funkcje i funkcje układu.
4. Odtwarzanie filmów z kamery internetowej
Czego się nauczysz
Z tego kroku dowiesz się, jak:
- Transmitowanie strumienia wideo za pomocą kamery internetowej.
- Manipulować odtwarzaniem strumienia.
- Używaj CSS i SVG do manipulowania filmem.
Pełna wersja tego kroku znajduje się w folderze step-01.
Odrobina HTML...
Dodaj elementy video
i 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 szczypta JavaScriptu
Dodaj ten kod do main.js w folderze js:
'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. Powinno być widoczne coś takiego (oczywiście widok z kamery internetowej):
Jak to działa
Po wywołaniu getUserMedia()
przeglądarka prosi użytkownika o pozwolenie na dostęp do kamery (jeśli po raz pierwszy wysłano prośbę o dostęp do kamery dla bieżącego źródła). Jeśli operacja się uda, zwracany jest obiekt MediaStream, którego można użyć przez element multimedialny w atrybucie srcObject
:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
}
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
Argument constraints
pozwala określić, jakie multimedia chcesz pobrać. W tym przykładzie mamy tylko filmy, bo dźwięk jest domyślnie wyłączony:
const mediaStreamConstraints = {
video: true,
};
Możesz zastosować ograniczenia, aby spełnić dodatkowe wymagania, takie jak rozdzielczość filmu:
const hdConstraints = {
video: {
width: {
min: 1280
},
height: {
min: 720
}
}
}
Specyfikacja MediaTrackConstraints zawiera listę wszystkich potencjalnych typów ograniczeń. Jednak nie wszystkie opcje są obsługiwane przez wszystkie przeglądarki. Jeśli wybrana rozdzielczość nie jest obsługiwana przez obecnie wybrany aparat, żądanie getUserMedia()
zostanie odrzucone za pomocą OverconstrainedError
, a użytkownik nie zostanie poproszony o zgodę na dostęp do aparatu.
Jeśli proces getUserMedia()
się powiedzie, strumień wideo z kamery internetowej zostanie ustawiony jako źródło elementu wideo:
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
Punkty bonusowe
- Obiekt
localStream
przekazany dogetUserMedia()
jest w zakresie globalnym, więc możesz go sprawdzić z konsoli przeglądarki. W tym celu otwórz konsolę, wpisz stream i naciśnij Return. (Aby wyświetlić konsolę w Chrome, naciśnij Ctrl + Shift + J lub Command + Option + J na Macu). - Co zwraca
localStream.getVideoTracks()
? - Zadzwoń pod numer
localStream.getVideoTracks()[0].stop()
. - Spójrz na obiekt ograniczeń. Co się stanie, gdy zmienisz wartość na
{audio: true, video: true}
? - Jaki jest rozmiar elementu wideo? Jak za pomocą JavaScriptu uzyskać naturalny rozmiar filmu, a nie rozmiar wyświetlanego obrazu? Użyj Narzędzi deweloperskich w Chrome, aby to sprawdzić.
- Spróbuj dodać do elementu wideo filtry CSS. 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%);
}
Zdobyte informacje
Wiesz już, jak:
- Nagrywaj obraz za pomocą kamery internetowej.
- Ustaw ograniczenia multimediów.
- Pomiń element wideo.
Pełna wersja tego kroku znajduje się w folderze step-01.
Wskazówki
- Nie zapomnij o atrybucie
autoplay
elementuvideo
. W przeciwnym razie zobaczysz tylko jedną klatkę. - Dostępnych jest znacznie więcej opcji w przypadku ograniczeń typu
getUserMedia()
. Zajrzyj na stronę demonstracyjną: webrtc.github.io/samples/src/content/peerconnection/constraints. Jak zobaczysz, w tej witrynie jest wiele interesujących przykładów WebRTC.
Sprawdzona metoda
- Upewnij się, że element wideo nie wychodzi poza kontener. Dodaliśmy
width
imax-width
, aby ustawić preferowany i maksymalny rozmiar filmu. Przeglądarka automatycznie obliczy wysokość:
video {
max-width: 100%;
width: 320px;
}
Następny krok
Masz film, ale jak go przesyłać strumieniowo? Sprawdź to w następnym kroku.
5. Strumieniowe przesyłanie wideo przy użyciu RTCPeerConnection
Czego się nauczysz
Z tego kroku dowiesz się, jak:
- Opis różnic między przeglądarkami a podkładką WebRTC – adapter.js.
- Używaj interfejsu RTCPeerConnection API do strumieniowego przesyłania wideo.
- Sterowanie nagrywaniem i strumieniowaniem multimediów.
Pełna wersja tego kroku znajduje się w folderze step-2.
Co to jest RTCPeerConnection?
RTCPeerConnection to interfejs API służący do tworzenia wywołań WebRTC w celu strumieniowego przesyłania danych wideo i audio oraz wymiany danych.
W tym przykładzie konfiguruje się połączenie między 2 obiektami RTCPeerConnection (nazywanymi równorzędnymi) na tej samej stronie.
Mało praktyczne, ale pozwala zrozumieć, jak działa RTCPeerConnection.
Dodawanie elementów wideo i przycisków sterujących
W pliku index.html zastąp pojedynczy element wideo dwoma elementami wideo i trzema 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 wyświetli strumień z getUserMedia()
, a drugi – ten sam film przesyłany strumieniowo przez połączenie RTCPeer. (W prawdziwej aplikacji jeden element wideo wyświetli strumień lokalny, a drugi – zdalny).
Dodaj podkładkę podrzędną.js.
Dodaj link do bieżącej wersji pliku adapter.js nad linkiem do 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>
Zainstaluj kod RTCPeerConnection
Zastąp main.js wersją z folderu step-02.
Zadzwoń
Otwórz plik index.html, kliknij przycisk Rozpocznij, aby nagrać film z kamery internetowej, a następnie kliknij Zadzwoń, aby nawiązać połączenie. W obu elementach wideo powinien być widoczny ten sam film (z Twojej kamery internetowej). Aby zobaczyć logowanie WebRTC, otwórz konsolę przeglądarki.
Jak to działa
To bardzo dużo...
WebRTC korzysta z interfejsu API RTCPeerConnection do konfigurowania połączenia strumieniowego przesyłania wideo między klientami WebRTC. Jest to tzw. peer.
W tym przykładzie 2 obiekty RTCPeerConnection znajdują się na tej samej stronie: pc1
i pc2
. Mało praktyczne, ale nadaje się do zademonstrowania działania interfejsów API.
Skonfigurowanie połączenia między równorzędnymi uczestnikami WebRTC obejmuje 3 czynności:
- Utwórz połączenie RTCPeerConnection dla każdego końca wywołania i dodaj strumień lokalny z
getUserMedia()
. - Pobieranie i udostępnianie informacji o sieci: potencjalne punkty końcowe połączenia są nazywane kandydatami ICE.
- Pobieraj i udostępniaj lokalne i zdalne opisy – metadane multimediów lokalnych w formacie SDP.
Załóżmy, że Alicja i Robert chcą skonfigurować czat wideo za pomocą RTCPeerConnection.
Najpierw Alicja i Robert wymieniają się informacjami o sieci. Wyrażenie „znajdowanie kandydatów” odnosi się do procesu wyszukiwania interfejsów i portów sieciowych za pomocą platformy ICE.
- Alicja tworzy obiekt RTCPeerConnection z modułem 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);
- Alicja wywołuje metodę
getUserMedia()
i dodaje do niego 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.');
- Moduł obsługi
onicecandidate
z kroku 1 jest wywoływany, gdy dostępne są kandydaci sieci. - Alicja wysyła do Roberta zserializowane dane kandydata. W prawdziwej aplikacji proces ten (nazywany sygnalizacją) odbywa się przez usługę przesyłania wiadomości – w jednym z dalszych kroków dowiesz się, jak to zrobić. Oczywiście w tym kroku 2 obiekty RTCPeerConnection znajdują się na tej samej stronie i mogą komunikować się bezpośrednio bez konieczności wysyłania wiadomości z zewnątrz.
- Gdy Robert otrzyma wiadomość od Alicji, dzwoni pod numer
addIceCandidate()
, by dodać kandydata do opisu zdalnego ucznia:
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}.`);
}
}
Połączenia równorzędne WebRTC muszą też znajdować i wymieniać lokalne i zdalne informacje o multimediach (audio i wideo), takie jak funkcje rozdzielczości i kodeków. Sygnalizacja dotycząca wymiany informacji o konfiguracji mediów jest przekazywana przez wymianę blobów metadanych, tzw. oferty i odpowiedzi, przy użyciu formatu protokołu Session Opis Protocol (SDP):
- Alicja uruchamia metodę RTCPeerConnection
createOffer()
. Zwrócona obietnica przedstawia RTCSessionDescription: opis lokalnej sesji Alicji:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- W przypadku powodzenia Alicja ustawia opis lokalny za pomocą parametru
setLocalDescription()
, a następnie wysyła opis sesji Robertowi za pomocą swojego kanału sygnału. - Jako opis zdalny Robert ustawia opis przesłany przez Alicję za pomocą funkcji
setRemoteDescription()
. - Robert uruchamia metodę RTCPeerConnection
createAnswer()
, przekazując do niej zdalny opis, który otrzymał od Alicji, aby można było wygenerować sesję lokalną zgodną z jej. ObietnicacreateAnswer()
przekazuje RTCSessionDescription: Robert ustawia to jako opis lokalny i wysyła go do Alicji. - Gdy Alicja otrzyma opis sesji Michała, ustawia go jako opis zdalny w aplikacji
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);
}
- Ping!
Punkty bonusowe
- Wejdź na chrome://webrtc-internals. Spowoduje to udostępnienie statystyk i danych debugowania WebRTC. (Pełną listę adresów URL w Chrome znajdziesz na chrome://about).
- Dobierz styl strony za pomocą CSS:
- Położyć filmy obok siebie.
- Przyciski powinny mieć taką samą szerokość, ale większy tekst.
- Upewnij się, że układ działa na urządzeniach mobilnych.
- W konsoli Narzędzi deweloperskich w Chrome otwórz listę
localStream
,localPeerConnection
iremotePeerConnection
. - W konsoli spójrz na listę
localPeerConnectionpc1.localDescription
. Jak wygląda format SDP?
Zdobyte informacje
Wiesz już, jak:
- Opis różnic między przeglądarkami a podkładką WebRTC – adapter.js.
- Używaj interfejsu RTCPeerConnection API do strumieniowego przesyłania wideo.
- Sterowanie nagrywaniem i strumieniowaniem multimediów.
- Udostępniaj informacje o multimediach i sieci między równorzędne, aby umożliwić wywołanie WebRTC.
Pełna wersja tego kroku znajduje się w folderze step-2.
Wskazówki
- Na tym etapie trzeba się bardzo wiele nauczyć. Więcej materiałów zawierających bardziej szczegółowe informacje o RTCPeerConnection znajdziesz na webrtc.org. Ta strona zawiera sugestie dotyczące struktur JavaScript – jeśli chcesz używać WebRTC, ale nie chcesz tworzyć wrangle w interfejsach API.
- Więcej informacji o podkładce adapter.js znajdziesz w repozytorium adaptera GitHub na GitHubie.
- Chcesz się dowiedzieć, jak wygląda najlepsza na świecie aplikacja do czatu wideo? Rzućmy okiem na AppRTC, czyli kanoniczną aplikację projektu WebRTC do wywołań WebRTC: app, code. Czas konfiguracji połączenia jest krótszy niż 500 ms.
Sprawdzona metoda
- Aby przygotować swój kod na przyszłość, użyj nowych interfejsów API opartych na Promise i za pomocą pliku adapter.js zapewnij zgodność z przeglądarkami, które ich nie obsługują.
Następny krok
Ten krok pokazuje, jak używać WebRTC do przesyłania strumieniowego wideo między równorzędnymi użytkownikami – ale to ćwiczenie w Codelabs dotyczy również danych.
W następnym kroku dowiesz się, jak przesyłać strumieniowo dowolne dane za pomocą RTCDataChannel.
6. Używanie RTCDataChannel do wymiany danych
Czego się nauczysz
- Jak wymieniać dane między punktami końcowymi WebRTC (równorzędnymi).
Pełna wersja tego kroku znajduje się w folderze step-03.
Zaktualizuj kod HTML
W tym kroku użyjesz kanałów danych WebRTC do przesyłania tekstu między 2 elementami textarea
na tej samej stronie. Nie jest to zbyt przydatne, ale pokazuje, jak za pomocą WebRTC można udostępniać dane oraz strumieniować wideo.
Usuń z pliku index.html elementy przycisku lub filmu i zastąp je poniższym 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>
Jeden obszar będzie służył do wpisywania tekstu, a drugi będzie wyświetlał tekst jako przesyłany strumieniowo między rówieśnikami.
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>
Zaktualizuj JavaScript
Zastąp main.js zawartością step-03/js/main.js.
Wypróbuj strumieniowe przesyłanie danych między peerem: otwórz plik index.html, naciśnij Rozpocznij, aby skonfigurować połączenie z peerem, wpisz tekst w polu textarea
po lewej, a następnie kliknij Wyślij, aby przesłać tekst przez kanały danych WebRTC.
Jak to działa
Ten kod wykorzystuje RTCPeerConnection i RTCDataChannel do wymiany wiadomości tekstowych.
Duża część kodu w tym kroku jest taka sama jak w przykładzie połączenia RTCPeerConnection.
Funkcje sendData()
i createConnection()
zawierają większość nowego kodu:
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 protokołu RTCDataChannel jest celowo podobna do składni WebSocket z metodą send()
i zdarzeniem message
.
Zwróć uwagę na korzystanie z: dataConstraint
. Kanały danych można skonfigurować tak, aby obsługiwać różne rodzaje udostępniania danych, na przykład traktować priorytetowo niezawodne dostarczanie zamiast wydajności. Więcej informacji o dostępnych opcjach znajdziesz na stronie Mozilla Developer Network.
Punkty bonusowe
- W przypadku SCTP protokół używany przez kanały danych WebRTC zapewnia domyślnie włączone i uporządkowane dostarczanie danych. Kiedy RTCDataChannel może zapewnić niezawodne dostarczanie danych, a kiedy wydajność może być ważniejsza, nawet jeśli oznacza to utratę części danych?
- Użyj arkuszy CSS, aby ulepszyć układ strony, i dodaj atrybut zastępczy do parametru „dataChannelReceive” pola tekstowego.
- Przetestuj stronę na urządzeniu mobilnym.
Zdobyte informacje
Wiesz już, jak:
- Nawiązywanie połączenia między 2 innymi równorzędnymi połączeniami WebRTC.
- Wymiana danych tekstowych między aplikacjami równorzędnymi.
Pełna wersja tego kroku znajduje się w folderze step-03.
Więcej informacji
- Kanały danych WebRTC (działające od kilku lat, ale nadal warto się z nimi zapoznać)
- Dlaczego wybrano SCTP na potrzeby kanału danych WebRTC?
Następny krok
Wiesz już, jak przekazywać dane między aplikacjami na tej samej stronie. Jak to jednak zrobić między różnymi komputerami? Najpierw musisz skonfigurować kanał sygnału, aby wymieniać wiadomości z metadanymi. Dowiedz się, jak to zrobić w następnym kroku.
7. Konfigurowanie usługi sygnalizacyjnej do wymiany wiadomości
Czego się nauczysz
Z tego kroku dowiesz się, jak:
- Użyj narzędzia
npm
, aby zainstalować zależności projektu zgodnie z opisem w pliku package.json. - Uruchom serwer Node.js i użyj parametru node-static do obsługi plików statycznych.
- Skonfiguruj usługę do przesyłania wiadomości w Node.js przy użyciu Socket.IO.
- Wykorzystaj to do utworzenia „pokoi” i wymienianie się wiadomościami.
Pełna wersja tego kroku znajduje się w folderze step-04.
Pojęcia
Aby skonfigurować i obsługiwać wywołanie WebRTC, klienty WebRTC (równorzędne) muszą wymienić metadane:
- Informacje o kandydatach (o sieci).
- wiadomości typu Offer i odpowiedzi zawierające informacje o multimediach, takie jak rozdzielczość czy kodeki;
Oznacza to, że przed rozpoczęciem bezpośredniego strumieniowania dźwięku, wideo lub danych wymagana jest wymiana metadanych. Ten proces jest nazywany sygnalizowaniem.
W poprzednich krokach obiekty RTCPeerConnection nadawcy i odbiorcy znajdują się na tej samej stronie, więc funkcja „sygnalizowanie” sprowadza się po prostu do przesyłania metadanych między obiektami.
W prawdziwej aplikacji połączenia RTCPeerConnections nadawcy i odbiorcy działają na stronach internetowych na różnych urządzeniach i potrzebujesz sposobu, aby mogli one komunikować się z metadanymi.
Do tego celu używasz serwera sygnałów, czyli serwera, który może przekazywać wiadomości między klientami WebRTC (równorzędnymi). Komunikaty to zwykły tekst: obiekty JavaScript w postaci ciągów znaków.
Warunek wstępny: zainstaluj Node.js
Aby wykonać następne kroki tego ćwiczenia w Codelabs (od step-04 do step-04), musisz uruchomić serwer na hoście lokalnym przy użyciu środowiska Node.js.
Node.js możesz pobrać i zainstalować, korzystając z tego linku lub za pomocą preferowanego menedżera pakietów.
Po zainstalowaniu możesz zaimportować zależności wymagane w następnych krokach (uruchomienie npm install
) oraz uruchomić niewielki serwer lokalny, by wykonać ćwiczenia z programowania (uruchomiono node index.js
). Te polecenia zostaną później wskazane, jeśli będą wymagane.
Informacje o aplikacji
WebRTC korzysta z interfejsu API JavaScript po stronie klienta, ale do użytku rzeczywistego wymaga również serwera sygnałów (komunikatów) oraz serwerów STUN i TURN. Więcej informacji znajdziesz tutaj.
W tym kroku utworzysz prosty serwer sygnałów Node.js wykorzystujący moduł Socket.IO Node.js oraz bibliotekę JavaScript do przesyłania wiadomości. Znajomość Node.js i Socket.IO będzie przydatna, ale nie jest kluczowa. komponenty komunikacji są bardzo proste.
W tym przykładzie serwer (aplikacja Node.js) został zaimplementowany w pliku index.js, a działający na nim klient (aplikacja internetowa) – w index.html.
Aplikacja Node.js w tym kroku ma 2 zadania.
Po pierwsze, działa jak przekaźnik wiadomości:
socket.on('message', function (message) {
log('Got message: ', message);
socket.broadcast.emit('message', message);
});
Po drugie, zarządza on „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 pełnoletnim wspólne korzystanie z pokoju.
HTML i JavaScript
Zaktualizuj plik index.html 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 nie zobaczysz niczego na stronie: logowanie odbywa się w konsoli przeglądarki. (Aby wyświetlić konsolę w Chrome, naciśnij Ctrl + Shift + J lub Command + Option + J na Macu).
Zastąp plik js/main.js następującym fragmentem:
'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 wtyczki Socket.IO do uruchamiania w Node.js
W pliku HTML możesz zauważyć, że używasz pliku Socket.IO:
<script src="/socket.io/socket.io.js"></script>
Na najwyższym poziomie katalogu 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" } }
To jest plik manifestu aplikacji, który informuje menedżera pakietów węzłów (npm
), które zależności projektu należy zainstalować.
Aby zainstalować zależności (np. /socket.io/socket.io.js
), uruchom w katalogu work w terminalu wiersza poleceń następujące polecenie:
npm install
Powinien się wyświetlić dziennik instalacji, który kończy się mniej więcej tak:
Jak widać, aplikacja npm
zainstalowała zależności zdefiniowane w pliku package.json.
Utwórz nowy plik index.js na najwyższym poziomie katalogu 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 następujące polecenie w katalogu work:
node index.js
W przeglądarce otwórz localhost:8080.
Za każdym razem, gdy otworzysz ten URL, zobaczysz prośbę o wpisanie nazwy pokoju. Aby dołączyć do tego samego pokoju, wybierz za każdym razem tę samą nazwę, na przykład „foo”.
Otwórz stronę nowej karty i ponownie otwórz adres localhost:8080. Wybierz tę samą nazwę pokoju.
Otwórz plik localhost:8080 na trzeciej karcie lub w trzecim oknie. Jeszcze raz wybierz tę samą nazwę pomieszczenia.
Sprawdź konsolę na każdej z kart – powinny wyświetlić się dane logowania z JavaScriptu powyżej.
Punkty bonusowe
- Jakie alternatywne mechanizmy komunikacji mogłyby działać? Jakie problemy mogą wystąpić podczas używania atrybutu „czyste” WebSocket?
- Jakie problemy mogą dotyczyć skalowania tej aplikacji? Czy możecie opracować metodę testowania tysięcy lub milionów zapytań o pokoje jednocześnie?
- Ta aplikacja pobiera nazwę pomieszczenia, używając prompta JavaScriptu. Znajdź sposób na uzyskanie nazwy pokoju z adresu URL. Jeśli na przykład wpiszesz localhost:8080/foo, pokój będzie miał nazwę
foo
.
Zdobyte informacje
Wiesz już, jak:
- Użyj npm do zainstalowania zależności projektu zgodnie z opisem w pliku package.json
- Za pomocą serwera Node.js można przesyłać pliki statyczne na serwer.
- Skonfiguruj usługę do przesyłania wiadomości w Node.js przy użyciu socket.io.
- Wykorzystaj to do utworzenia „pokoi” i wymienianie się wiadomościami.
Pełna wersja tego kroku znajduje się w folderze step-04.
Więcej informacji
- Przykładowe repozytorium Socket.io czatu
- WebRTC w świecie rzeczywistym: STUN, TURN i sygnalizowanie
- Termin „sygnalizowanie” w WebRTC
Następny krok
Dowiedz się, jak za pomocą sygnałów umożliwić 2 użytkownikom nawiązanie połączenia równorzędnego.
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ą wtyczki Socket.IO w Node.js
- Używaj tej usługi do wymiany metadanych WebRTC między elementami równorzędnymi.
Pełna wersja tego kroku znajduje się w folderze step-05.
Zastąp HTML i JavaScript
Zastąp zawartość pliku index.html 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>
<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ą step-05/js/main.js.
Uruchamianie serwera Node.js
Jeśli nie wykonujesz ćwiczenia z programowania w katalogu work, być może musisz zainstalować zależności z folderu step-05 lub bieżącego folderu roboczego. Uruchom to polecenie w katalogu roboczym:
npm install
Jeśli serwer Node.js nie działa, po zainstalowaniu uruchom go, wywołując to polecenie z katalogu work:
node index.js
Upewnij się, że używasz wersji pliku index.js z poprzedniego kroku implementującego Socket.IO. Więcej informacji o węźle i gniazdie wejścia-wyjścia znajdziesz w sekcji „Konfigurowanie usługi sygnalizacji umożliwiającej wymianę wiadomości”.
W przeglądarce otwórz localhost:8080.
Otwórz ponownie localhost:8080 w nowej karcie lub nowym oknie. Jeden element wideo wyświetli strumień lokalny z: getUserMedia()
, a drugi – „zdalny” wideo przesyłane strumieniowo przez RTCPeerconnection.
Wyświetl logowanie w konsoli przeglądarki.
Punkty bonusowe
- Ta aplikacja obsługuje tylko czat wideo twarzą w twarz. Jak można zmienić projekt, aby więcej niż jedna osoba mogła korzystać z tego samego pokoju czatu wideo?
- W przykładzie nazwa pokoju foo jest zakodowana na stałe. Jak najlepiej włączyć inne nazwy pomieszczeń?
- W jaki sposób użytkownicy mogą udostępnić nazwę pokoju? Spróbuj stworzyć alternatywę dla udostępniania nazw pomieszczeń.
- Jak możesz zmienić aplikację
Zdobyte informacje
Wiesz już, jak:
- Uruchom usługę sygnalizacyjną WebRTC za pomocą wtyczki Socket.IO w Node.js.
- Używaj tej usługi do wymiany metadanych WebRTC między elementami równorzędnymi.
Pełna wersja tego kroku znajduje się w folderze step-05.
Wskazówki
- Statystyki i dane debugowania WebRTC są dostępne na stronie chrome://webrtc-internals.
- Strona test.webrtc.org pozwala sprawdzić środowisko lokalne oraz przetestować kamerę i mikrofon.
- Jeśli masz dziwne problemy z buforowaniem, wykonaj te czynności:
- Twarde odświeżenie powoduje twarde odświeżenie, przytrzymując Ctrl i klikając przycisk Załaduj ponownie.
- Ponowne uruchomienie przeglądarki
- Uruchom
npm cache clean
z poziomu wiersza poleceń.
Następny krok
Dowiedz się, jak zrobić zdjęcie, pobrać dane i udostępnić je innym zdalnym znajomym.
9. Zrób zdjęcie i udostępnij je przez kanał danych
Czego się nauczysz
Z tego kroku dowiesz się, jak:
- Zrób zdjęcie i pobierz z niego dane za pomocą elementu canvas.
- Wymiana danych obrazu z użytkownikiem zdalnym.
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 przy użyciu RTCDataChannel.
Ten krok umożliwia udostępnianie całych plików: w tym przykładzie są to zdjęcia zrobione za pomocą getUserMedia()
.
Najważniejsze części tego kroku są następujące:
- Utwórz kanał danych. Pamiętaj, że na tym etapie do połączenia równorzędnego nie dodasz żadnych strumieni multimediów.
- Nagraj strumień wideo z kamery internetowej użytkownika za pomocą aplikacji
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);
});
}
- Gdy użytkownik kliknie przycisk Przyciągnij, pobierze podsumowanie (klatkę wideo) ze strumienia wideo i wyświetli 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);
}
- Gdy użytkownik kliknie przycisk Wyślij, przekonwertuj obraz na bajty i wyślij go przez kanał 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));
}
}
- Strona odbierająca konwertuje bajty wiadomości kanału danych z powrotem na obraz i wyświetla obraz 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 & 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 wykonujesz ćwiczenia z programowania w katalogu work, być może musisz zainstalować zależności z folderu step-06 lub bieżącego folderu roboczego. Po prostu uruchom to polecenie w katalogu roboczym:
npm install
Jeśli serwer Node.js nie działa po zainstalowaniu, uruchom go, wywołując to polecenie z katalogu work:
node index.js
Sprawdź, czy używasz wersji index.js, która obsługuje Socket.IO, i pamiętaj o ponownym uruchomieniu serwera Node.js, jeśli wprowadzisz zmiany. Więcej informacji o węźle i gniazdie wejścia-wyjścia znajdziesz w sekcji „Konfigurowanie usługi sygnalizacji umożliwiającej wymianę 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 ikonę Wyślij, a następnie spójrz na obszar Przychodzące na innej karcie u dołu strony. Aplikacja przenosi zdjęcia między kartami.
Powinien pojawić się ekran podobny do tego:
Punkty bonusowe
- Jak zmienić kod, aby można było udostępniać pliki dowolnego typu?
Więcej informacji
- MediaStream Image Capture API: interfejs API do robienia zdjęć i sterowania aparatami – już wkrótce w Twojej przeglądarce.
- Interfejs MediaRecorder API do nagrywania dźwięku i filmów: prezentacja, dokumentacja.
Zdobyte informacje
- Jak zrobić zdjęcie i uzyskać z niego dane za pomocą elementu canvas.
- jak wymieniać się tymi danymi z użytkownikiem zdalnym;
Pełna wersja tego kroku znajduje się w folderze step-06.
10. Gratulacje
Udało Ci się stworzyć aplikację do strumieniowego przesyłania filmów i wymiany danych w czasie rzeczywistym.
Zdobyte informacje
Dzięki temu ćwiczeniu w programie omówiliśmy, jak:
- Nagrywaj obraz za pomocą kamery internetowej.
- Strumieniowe przesyłanie wideo przy użyciu protokołu RTCPeerConnection.
- Przesyłaj strumieniowo dane za pomocą RTCDataChannel.
- Skonfiguruj usługę sygnalizacyjną do wymiany wiadomości.
- Połącz połączenie równorzędne i sygnalizowanie.
- Zrób zdjęcie i udostępnij je przez kanał danych.
Dalsze kroki
- Przyjrzyj się kodowi i architekturze kanonicznej aplikacji czatu WebRTC: aplikacja, kod.
- Wypróbuj prezentacje na żywo ze strony github.com/webrtc/samples.
Więcej informacji
- W witrynie webrtc.org dostępne są zasoby ułatwiające rozpoczęcie korzystania z WebRTC.