Comunicazione in tempo reale con WebRTC

1. Introduzione

WebRTC è un progetto open source per consentire la comunicazione in tempo reale di audio, video e dati nelle app web e native.

WebRTC ha diverse API JavaScript: fai clic sui link per visualizzare le demo.

Dove posso utilizzare WebRTC?

In Firefox, Opera e Chrome su computer e Android. WebRTC è disponibile anche per le app native su iOS e Android.

Che cos'è la segnalazione?

WebRTC utilizza RTCPeerConnection per comunicare in modalità flusso di dati tra i browser, ma necessita anche di un meccanismo per coordinare la comunicazione e inviare messaggi di controllo, un processo noto come segnalazione. I metodi e i protocolli di segnalazione non sono specificati da WebRTC. In questo codelab utilizzerai Socket.IO per i messaggi, ma esistono molte alternative.

Che cosa sono STUN e TURN?

WebRTC è progettato per funzionare in modalità peer-to-peer, quindi gli utenti possono connettersi utilizzando il percorso più diretto possibile. Tuttavia, WebRTC è progettato per far fronte al networking del mondo reale: le applicazioni client devono attraversare gateway NAT e firewall e il networking peer-to-peer ha bisogno di fallback nel caso in cui la connessione diretta non vada a buon fine. Nell'ambito di questo processo, le API WebRTC utilizzano i server STUN per ottenere l'indirizzo IP del tuo computer e i server TURN come server di inoltro nel caso in cui la comunicazione peer-to-peer non vada a buon fine. (WebRTC nel mondo reale spiega più dettagliatamente.)

WebRTC è sicuro?

La crittografia è obbligatoria per tutti i componenti WebRTC e le relative API JavaScript possono essere utilizzate solo da origini sicure (HTTPS o localhost). I meccanismi di segnalazione non sono definiti dagli standard WebRTC, quindi è tua responsabilità utilizzare protocolli sicuri.

2. Panoramica

Crea un'app per ottenere video, acquisire istantanee con la webcam e condividerle in modalità peer-to-peer tramite WebRTC. Lungo il percorso imparerai a utilizzare le API WebRTC principali e a configurare un server di messaggistica utilizzando Node.js.

Obiettivi didattici

  • Guarda il video dalla webcam
  • Trasmetti video in streaming con RTCPeerConnection
  • Trasmetti il flusso di dati con RTCDataChannel
  • Configurare un servizio di segnalazione per lo scambio di messaggi
  • Combinare la connessione peer e l'indicatore
  • Scatta una foto e condividila tramite un canale di dati

Che cosa ti serve

  • Chrome 47 o versioni successive
  • Server web per Chrome oppure utilizza il tuo server web preferito.
  • Il codice campione
  • Un editor di testo
  • Conoscenza di base di HTML, CSS e JavaScript

3. recupera il codice campione

Scarica il codice

Se hai dimestichezza con Git, puoi scaricare il codice di questo codelab da GitHub clonandolo:

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

In alternativa, fai clic sul seguente pulsante per scaricare un file ZIP con il codice:

Apri il file ZIP scaricato. Verrà aperta una cartella di progetto (adaptive-web-media) contenente una cartella per ogni passaggio di questo codelab, insieme a tutte le risorse necessarie.

Farai tutto il lavoro di programmazione nella directory denominata work.

Le cartelle step-nn contengono una versione completa per ogni passaggio di questo codelab. che vengono utilizzate come riferimento.

Installa e verifica il server web

Anche se puoi utilizzare liberamente il tuo server web, questo codelab è stato progettato per funzionare bene con il server web Chrome. Se non hai ancora installato l'app, puoi installarla dal Chrome Web Store.

6ddeb4aee53c0f0e.png

Dopo aver installato l'app Server web per Chrome, fai clic sulla scorciatoia App di Chrome nella barra dei preferiti, in una pagina Nuova scheda o in Avvio applicazioni:

1d2b4aa977ab7e24.png

Fai clic sull'icona del server web:

27fce4494f641883.png

Verrà visualizzata questa finestra di dialogo, che ti consente di configurare il server web locale:

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

Fai clic sul pulsante SCEGLI CARTELLA e seleziona la cartella di lavoro appena creata. In questo modo potrai visualizzare il tuo lavoro in Chrome tramite l'URL evidenziato nella finestra di dialogo Server web nella sezione URL server web.

In Opzioni, seleziona la casella accanto a Mostra automaticamente index.html come mostrato di seguito:

Schermata 18-02-2016 alle 11.56.30 AM.png

Successivamente, arresta e riavvia il server facendo scorrere il pulsante di attivazione/disattivazione con l'etichetta Server web: AVVIATO verso sinistra e poi di nuovo verso destra.

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

Ora visita il tuo sito di lavoro nel browser web facendo clic sull'URL del server web evidenziato. Dovresti visualizzare una pagina simile alla seguente, che corrisponde a work/index.html:

18a705cb6bb5181.png

Ovviamente questa app non ha ancora realizzato nulla di interessante, perché al momento è solo uno scheletro minimo che stiamo utilizzando per verificare che il tuo server web funzioni correttamente. Aggiungerai elementi di funzionalità e layout nei passaggi successivi.

4. Riprodurre in streaming il video dalla webcam

Obiettivi didattici

In questo passaggio, scoprirai come:

  • Guarda uno stream video dalla tua webcam.
  • Manipolare la riproduzione dello stream.
  • Utilizzare CSS e SVG per manipolare i video.

La versione completa di questo passaggio si trova nella cartella step-01.

Un pizzico di HTML...

Aggiungi un elemento video e un elemento script a index.html nella directory di lavoro:

<!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>

...e un pizzico di JavaScript

Aggiungi quanto segue a main.js nella cartella 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);

Prova

Apri la pagina index.html nel browser e dovresti vedere qualcosa del genere (ovviamente con la tua webcam):

9297048e43ed0f3d.png

Come funziona

Dopo la chiamata getUserMedia(), il browser richiede all'utente l'autorizzazione ad accedere alla fotocamera (se è la prima volta che viene richiesto l'accesso alla fotocamera per l'origine attuale). In caso di esito positivo, viene restituito un MediaStream, che può essere utilizzato da un elemento multimediale tramite l'attributo srcObject:

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


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

L'argomento constraints ti consente di specificare quali contenuti multimediali ricevere. In questo esempio, solo il video, dato che l'audio è disattivato per impostazione predefinita:

const mediaStreamConstraints = {
  video: true,
};

Puoi utilizzare i vincoli per soddisfare requisiti aggiuntivi come la risoluzione video:

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

La specifica MediaTrackConstraints elenca tutti i potenziali tipi di vincoli, anche se non tutte le opzioni sono supportate da tutti i browser. Se la risoluzione richiesta non è supportata dalla videocamera attualmente selezionata, getUserMedia() verrà rifiutato con un OverconstrainedError e all'utente non verrà chiesto di autorizzare l'accesso alla fotocamera.

Se getUserMedia() ha esito positivo, il video stream proveniente dalla webcam viene impostato come origine dell'elemento video:

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

Punti bonus

  • L'oggetto localStream passato a getUserMedia() si trova in ambito globale, quindi puoi controllarlo dalla console del browser: apri la console, digita stream e premi Invio. Per visualizzare la console in Chrome, premi Ctrl-Maiusc-J o Comando-Opzione-J se utilizzi un Mac.
  • Che cosa restituisce localStream.getVideoTracks()?
  • Prova a chiamare localStream.getVideoTracks()[0].stop().
  • Osserva l'oggetto vincoli: cosa succede quando lo modifichi in {audio: true, video: true}?
  • Quali sono le dimensioni dell'elemento video? Come puoi ricavare da JavaScript le dimensioni naturali del video invece di quelle di visualizzazione? Utilizza Chrome DevTools per verificare.
  • Prova ad aggiungere filtri CSS all'elemento video. Ad esempio:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Prova ad aggiungere filtri SVG. Ad esempio:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Che cosa hai imparato

In questo passaggio hai imparato a:

  • Riprendi il video dalla tua webcam.
  • Imposta vincoli multimediali.
  • Mettersi in confusione con l'elemento video.

La versione completa di questo passaggio si trova nella cartella step-01.

Suggerimenti

  • Non dimenticare l'attributo autoplay nell'elemento video. Senza questo, vedrai un solo frame.
  • Esistono molte altre opzioni per i vincoli getUserMedia(). Dai un'occhiata alla demo alla pagina webrtc.github.io/samples/src/content/peerconnection/constraints. Come noterai, sul sito esistono molti esempi di WebRTC interessanti.

Best practice

  • Assicurati che l'elemento video non superi il suo contenitore. Abbiamo aggiunto width e max-width per impostare una dimensione preferita e una dimensione massima per il video. Il browser calcolerà automaticamente l'altezza:
video {
  max-width: 100%;
  width: 320px;
}

Successivo

Hai i video, ma come puoi riprodurli in streaming? Scoprilo nel prossimo passaggio.

5. Trasmetti video in streaming con RTCPeerConnection

Obiettivi didattici

In questo passaggio, scoprirai come:

  • Elimina le differenze del browser con lo shim WebRTC adapter.js.
  • Utilizzare l'API RTCPeerConnection per lo streaming di video.
  • Controlla l'acquisizione e lo streaming di contenuti multimediali.

La versione completa di questo passaggio è disponibile nella cartella step-2.

Che cos'è RTCPeerConnection?

RTCPeerConnection è un'API per effettuare chiamate WebRTC per trasmettere in streaming video e audio e scambiare dati.

In questo esempio viene impostata una connessione tra due oggetti RTCPeerConnection (noti come peer) nella stessa pagina.

Non molto pratico, ma buono per capire come funziona RTCPeerConnection.

Aggiungere elementi video e pulsanti di controllo

In index.html, sostituisci il singolo elemento video con due elementi video e tre pulsanti:

<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>

Un elemento video mostrerà lo stream di getUserMedia() e l'altro mostrerà lo stesso video trasmesso in streaming tramite RTCPeerconnection. In un'applicazione reale, un elemento video visualizza lo stream locale e l'altro lo stream remoto.

Aggiungi lo shim Adapter.js

Aggiungi un link alla versione corrente di adapter.js sopra il link a main.js:

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

Ora il file Index.html dovrebbe avere il seguente aspetto:

<!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>

Installa il codice RTCPeerConnection

Sostituisci main.js con la versione indicata nella cartella step-02.

Fai una telefonata

Apri il file index.html, fai clic sul pulsante Avvia per scaricare il video dalla webcam e fai clic su Chiama per stabilire la connessione peer. Dovresti vedere lo stesso video (dalla webcam) in entrambi gli elementi video. Visualizza la console del browser per vedere il logging WebRTC.

Come funziona

Questo passaggio fa molto...

WebRTC utilizza l'API RTCPeerConnection per configurare una connessione per lo streaming di video tra client WebRTC, noti come peer.

In questo esempio, i due oggetti RTCPeerConnection si trovano nella stessa pagina: pc1 e pc2. Non molto d'uso pratico, ma buono per dimostrare come funzionano le API.

La configurazione di una chiamata tra peer WebRTC prevede tre attività:

  • Crea una RTCPeerConnection per ogni fine della chiamata e, a ciascuna estremità, aggiungi lo stream locale da getUserMedia().
  • Acquisizione e condivisione di informazioni di rete: potenziali endpoint di connessione sono noti come candidati ICE.
  • Ottenere e condividere descrizioni locali e remote: metadati relativi ai contenuti multimediali locali in formato SDP.

Immagina che Alice e Bob vogliano utilizzare RTCPeerConnection per impostare una videochiamata.

Innanzitutto, Alice e Bob si scambiano informazioni di rete. L'espressione "trovare candidati" si riferisce al processo di ricerca delle interfacce di rete e delle porte mediante il framework ICE.

  1. Alice crea un oggetto RTCPeerConnection con un gestore onicecandidate (addEventListener('icecandidate')). Corrisponde al seguente codice di main.js:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice chiama getUserMedia() e aggiunge lo stream trasmesso a questo indirizzo:
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. Il gestore onicecandidate del passaggio 1 viene chiamato quando i candidati di rete diventano disponibili.
  2. Alice invia a Bob i dati serializzati del candidato. In un'applicazione reale, questa procedura (nota come segnalazione) avviene tramite un servizio di messaggistica. Scoprirai come farlo in un passaggio successivo. Naturalmente, in questo passaggio i due oggetti RTCPeerConnection si trovano nella stessa pagina e possono comunicare direttamente senza bisogno di messaggi esterni.
  3. Quando Bob riceve un messaggio per il candidato da Alice, chiama addIceCandidate() per aggiungere il candidato alla descrizione remota del peer:
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}.`);
  }
}

I peer WebRTC devono anche rilevare e scambiare informazioni multimediali audio e video locali e remoti, come la risoluzione e le funzionalità di codec. La segnalazione dello scambio di informazioni sulla configurazione dei contenuti multimediali avviene con lo scambio di blob di metadati, noti come offer e answer, utilizzando il formato Session Description Protocol, noto come SDP:

  1. Alice esegue il metodo createOffer() RTCPeerConnection. La promessa restituita fornisce una RTCSessionDescription: descrizione della sessione locale di Alice:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. In caso di esito positivo, Alice imposta la descrizione locale utilizzando setLocalDescription(), quindi invia questa descrizione della sessione a Roberto tramite il canale di segnalazione.
  2. Bob imposta la descrizione inviata da Alice come descrizione remota utilizzando setRemoteDescription().
  3. Roberto esegue il metodo createAnswer() RTCPeerConnection, passando la descrizione remota che ha ricevuto da Alice, in modo da generare una sessione locale compatibile con la sua. La promessa createAnswer() trasmette una RTCSessionDescription: Bob la imposta come descrizione locale e la invia ad Alice.
  4. Quando Alice ottiene la descrizione della sessione di Roberto, la imposta come descrizione remota con 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!

Punti bonus

  1. Visita chrome://webrtc-internals. In questo modo vengono forniti statistiche e dati di debug WebRTC. Puoi trovare un elenco completo degli URL di Chrome all'indirizzo chrome://about.
  2. Applica uno stile alla pagina con CSS:
  • Posiziona i video uno accanto all'altro.
  • Imposta la stessa larghezza per i pulsanti, ma con un testo più grande.
  • Assicurati che il layout funzioni sui dispositivi mobili.
  1. Nella console Chrome DevTools, dai un'occhiata a localStream, localPeerConnection e remotePeerConnection.
  2. Nella console, osserva localPeerConnectionpc1.localDescription. Che aspetto ha il formato SDP?

Che cosa hai imparato

In questo passaggio hai imparato a:

  • Elimina le differenze del browser con lo shim WebRTC adapter.js.
  • Utilizzare l'API RTCPeerConnection per lo streaming di video.
  • Controlla l'acquisizione e lo streaming di contenuti multimediali.
  • Condividi informazioni multimediali e di rete tra peer per abilitare una chiamata WebRTC.

La versione completa di questo passaggio è disponibile nella cartella step-2.

Suggerimenti

  • C'è molto da imparare in questo passaggio. Per trovare altre risorse che illustrano RTCPeerConnection in modo più dettagliato, visita webrtc.org. Questa pagina include suggerimenti per framework JavaScript, se vuoi utilizzare WebRTC, ma non vuoi eseguire il wrangling delle API.
  • Scopri di più sullo shim adattatore.js dal repo GitHub ader.js.
  • Vuoi scoprire come si presenta la migliore app per video chat al mondo? Dai un'occhiata ad AppRTC, l'app canonica del progetto WebRTC per le chiamate WebRTC: app, code. Il tempo di configurazione della chiamata è inferiore a 500 ms.

Best practice

  • Per rendere il codice a prova di futuro, utilizza le nuove API basate su Promise e abilita la compatibilità con i browser che non li supportano utilizzando adapter.js.

Successivo

Questo passaggio mostra come utilizzare WebRTC per trasmettere video in streaming tra peer, ma questo codelab riguarda anche i dati.

Nel passaggio successivo scoprirai come trasmettere in streaming dati arbitrari utilizzando RTCDataChannel.

6. Utilizzo di RTCDataChannel per lo scambio di dati

Obiettivi didattici

  • Come scambiare dati tra endpoint WebRTC (peer).

La versione completa di questo passaggio è disponibile nella cartella step-03.

Aggiorna il codice HTML

Per questo passaggio, utilizzerai i canali di dati WebRTC per inviare testo tra due elementi textarea sulla stessa pagina. Non è molto utile, ma dimostra come sia possibile utilizzare WebRTC per condividere dati e video in streaming.

Rimuovi gli elementi video e pulsante da index.html e sostituiscili con il seguente codice 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>

Un'area di testo verrà utilizzata per inserire il testo, l'altra verrà visualizzato come flusso di testo tra app peer.

Ora index.html dovrebbe avere il seguente aspetto:

<!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>

Aggiornare il codice JavaScript

Sostituisci main.js con i contenuti di step-03/js/main.js.

Prova i flussi di dati tra peer: apri index.html, premi Avvia per configurare la connessione peer, inserisci del testo nella textarea a sinistra e poi fai clic su Invia per trasferire il testo utilizzando i canali di dati WebRTC.

Come funziona

Questo codice utilizza RTCPeerConnection e RTCDataChannel per consentire lo scambio di messaggi di testo.

Gran parte del codice in questo passaggio è uguale a quello dell'esempio RTCPeerConnection.

Le funzioni sendData() e createConnection() includono la maggior parte del nuovo codice:

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);
}

La sintassi di RTCDataChannel è volutamente simile a quella di WebSocket, con un metodo send() e un evento message.

Nota l'utilizzo di dataConstraint. I canali di dati possono essere configurati in modo da abilitare diversi tipi di condivisione dei dati, ad esempio dando priorità a una distribuzione affidabile rispetto alle prestazioni. Puoi trovare ulteriori informazioni sulle opzioni disponibili all'indirizzo Mozilla Developer Network.

Punti bonus

  1. Con SCTP, il protocollo utilizzato dai canali di dati WebRTC, affidabile e ordinato, è attivo per impostazione predefinita. Quando RTCDataChannel potrebbe dover fornire una distribuzione affidabile dei dati e quando il rendimento potrebbe essere più importante, anche se questo comporta la perdita di alcuni dati?
  2. Utilizza il CSS per migliorare il layout della pagina e aggiungi un attributo segnaposto a "dataChannelReceive" area di testo.
  3. Testa la pagina su un dispositivo mobile.

Che cosa hai imparato

In questo passaggio hai imparato a:

  • Stabilisci una connessione tra due peer WebRTC.
  • Scambiare dati di testo tra le app peer.

La versione completa di questo passaggio è disponibile nella cartella step-03.

Scopri di più

Successivo

Hai imparato a scambiare dati tra peer sulla stessa pagina, ma come puoi farlo tra computer diversi? Innanzitutto, devi configurare un canale di segnalazione per lo scambio di messaggi di metadati. Scopri come nel prossimo passaggio.

7. Configurare un servizio di segnalazione per lo scambio di messaggi

Obiettivi didattici

In questo passaggio, scoprirai come:

  • Utilizza npm per installare le dipendenze del progetto come specificato in package.json
  • Esegui un server Node.js e utilizza il nodo node-static per pubblicare i file statici.
  • Configura un servizio di messaggistica su Node.js utilizzando Socket.IO.
  • Utilizzalo per creare "stanze" e scambiare messaggi.

La versione completa di questo passaggio è disponibile nella cartella step-04.

Concetti

Per configurare e gestire una chiamata WebRTC, i client WebRTC (peer) devono scambiare metadati:

  • Informazioni sul candidato (rete).
  • Messaggi con offerte e risposte che forniscono informazioni sui contenuti multimediali, come risoluzione e codec.

In altre parole, è necessario uno scambio di metadati prima che possa avvenire lo streaming peer-to-peer di audio, video o dati. Questa procedura è chiamata segnalazione.

Nei passaggi precedenti, gli oggetti RTCPeerConnection mittente e destinatario si trovano nella stessa pagina, quindi "segnalazione" è sufficiente trasferire i metadati tra gli oggetti.

In un'applicazione reale, le RTCPeerConnection del mittente e del destinatario vengono eseguite in pagine web su dispositivi diversi e devi trovare un modo per comunicare i metadati.

Per farlo, utilizzi un server di segnalazione, ovvero un server che può passare messaggi tra client WebRTC (peer). I messaggi effettivi sono testo normale: oggetti JavaScript sotto forma di stringa.

Prerequisiti: installazione di Node.js

Per eseguire i passaggi successivi di questo codelab (cartelle da step-04 a step-06) devi eseguire un server su localhost utilizzando Node.js.

Puoi scaricare e installare Node.js da questo link o tramite il tuo gestore di pacchetti che preferisci.

Dopo l'installazione, potrai importare le dipendenze richieste per i passaggi successivi (esecuzione di npm install), nonché eseguire un piccolo server localhost per eseguire il codelab (esecuzione di node index.js). Questi comandi verranno indicati in seguito, quando saranno richiesti.

Informazioni sull'app

WebRTC utilizza un'API JavaScript lato client, ma per l'utilizzo reale richiede anche un server di segnalazione (di messaggistica), nonché server STUN e TURN. Puoi scoprire di più qui.

In questo passaggio creerai un semplice server di segnalazione Node.js utilizzando il modulo Node.js Socket.IO e la libreria JavaScript per la messaggistica. L'esperienza con Node.js e Socket.IO sarà utile, ma non fondamentale. i componenti dei messaggi sono molto semplici.

In questo esempio, il server (l'applicazione Node.js) è implementato in index.js e il client che viene eseguito su di esso (l'app web) è implementato in index.html.

L'applicazione Node.js in questo passaggio prevede due attività.

Innanzitutto, funge da inoltro di messaggi:

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

In secondo luogo, gestisce le "stanze virtuali" per le chat video 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);
}

La nostra semplice applicazione WebRTC consente a un massimo di due peer di condividere una stanza.

HTML e JavaScript

Aggiorna index.html in modo che abbia il seguente aspetto:

<!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>

In questo passaggio non verrà visualizzato nulla nella pagina: tutta la registrazione viene eseguita nella console del browser. Per visualizzare la console in Chrome, premi Ctrl-Maiusc-J o Comando-Opzione-J se utilizzi un Mac.

Sostituisci js/main.js con quanto segue:

'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);
});

Configura Socket.IO per l'esecuzione su Node.js

Nel file HTML potresti aver notato che stai utilizzando un file Socket.IO:

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

Al livello superiore della tua directory work, crea un file denominato package.json con i seguenti contenuti:

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

Si tratta di un manifest dell'app che indica a Node Package Manager (npm) quali dipendenze del progetto installare.

Per installare le dipendenze (ad esempio /socket.io/socket.io.js), esegui il comando seguente dal terminale a riga di comando nella directory work:

npm install

Dovresti vedere un log di installazione che termina in questo modo:

3ab06b7bcc7664b9.png

Come puoi vedere, npm ha installato le dipendenze definite in package.json.

Crea un nuovo file index.js al livello superiore della tua directory work (non nella directory js) e aggiungi il seguente codice:

'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);
        }
      });
    }
  });

});

Dal terminale della riga di comando, esegui questo comando nella directory work:

node index.js

Nel browser, apri localhost:8080.

Ogni volta che apri questo URL, ti verrà chiesto di inserire il nome della stanza. Per partecipare alla stessa stanza virtuale, scegli ogni volta lo stesso nome, ad esempio "foo".

Apri la pagina Nuova scheda e apri di nuovo localhost:8080. Scegli lo stesso nome per la stanza.

Apri localhost:8080 in una terza scheda o finestra. Scegli di nuovo lo stesso nome per la stanza.

Controlla la console in ogni scheda: dovresti vedere la registrazione dal codice JavaScript in alto.

Punti bonus

  1. Quali meccanismi di messaggistica alternativi potrebbero essere possibili? Quali problemi potresti riscontrare quando utilizzi lo stato WebSocket?
  2. Quali problemi potrebbero essere coinvolti nella scalabilità di questa applicazione? È possibile sviluppare un metodo per testare migliaia o milioni di richieste di stanze simultanee?
  3. Questa app utilizza un prompt JavaScript per recuperare il nome di una stanza. Trova un modo per recuperare il nome della stanza dall'URL. Ad esempio, localhost:8080/foo assegna il nome della stanza foo.

Che cosa hai imparato

In questo passaggio hai imparato a:

  • Usa npm per installare le dipendenze del progetto come specificato in package.json
  • Esegui un server Node.js per i file statici.
  • Configurare un servizio di messaggistica su Node.js utilizzando socket.io.
  • Utilizzalo per creare "stanze" e scambiare messaggi.

La versione completa di questo passaggio è disponibile nella cartella step-04.

Scopri di più

Successivo

Scopri come utilizzare la segnalazione per consentire a due utenti di stabilire una connessione peer.

8. Combinare la connessione peer e l'indicatore

Obiettivi didattici

In questo passaggio, scoprirai come:

  • Esegui un servizio di segnalazione WebRTC utilizzando Socket.IO in esecuzione su Node.js
  • Utilizza questo servizio per scambiare metadati WebRTC tra peer.

La versione completa di questo passaggio è disponibile nella cartella step-05.

Sostituisci HTML e JavaScript

Sostituisci i contenuti di index.html con quanto segue:

<!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>

Sostituisci js/main.js con i contenuti di step-05/js/main.js.

Esegui il server Node.js

Se non stai seguendo questo codelab dalla tua directory work, potresti dover installare le dipendenze per la cartella step-05 o la tua cartella di lavoro attuale. Esegui questo comando dalla directory di lavoro:

npm install

Una volta installato, se il tuo server Node.js non è in esecuzione, avvialo chiamando il seguente comando nella directory work:

node index.js

Assicurati di utilizzare la versione di index.js del passaggio precedente che implementa Socket.IO. Per ulteriori informazioni su I/O di nodi e socket, consulta la sezione "Configurare un servizio di segnalazione per lo scambio di messaggi".

Nel browser, apri localhost:8080.

Apri di nuovo localhost:8080 in una nuova scheda o finestra. Un elemento video mostrerà lo stream locale di getUserMedia(), mentre l'altro mostrerà il "telecomando" video trasmessi in streaming tramite RTCPeerconnection.

Visualizza il logging nella console del browser.

Punti bonus

  1. Questa applicazione supporta solo le chat video a due. Come potresti cambiare il design per consentire a più di una persona di condividere la stessa stanza virtuale per videochat?
  2. L'esempio ha il nome della stanza foo hardcoded. Qual è il modo migliore per attivare altri nomi di stanze?
  3. In che modo gli utenti condividono il nome della stanza? Prova a creare un'alternativa alla condivisione dei nomi delle stanze.
  4. Come potresti cambiare l'app?

Che cosa hai imparato

In questo passaggio hai imparato a:

  • Eseguire un servizio di segnalazione WebRTC utilizzando Socket.IO in esecuzione su Node.js.
  • Utilizza questo servizio per scambiare metadati WebRTC tra peer.

La versione completa di questo passaggio è disponibile nella cartella step-05.

Suggerimenti

  • Le statistiche e i dati di debug WebRTC sono disponibili all'indirizzo chrome://webrtc-internals.
  • test.webrtc.org può essere utilizzato per controllare il tuo ambiente locale e testare videocamera e microfono.
  • Se riscontri particolari problemi con la memorizzazione nella cache, prova a procedere nel seguente modo:
  • Esegui un aggiornamento forzato tenendo premuto Ctrl e facendo clic sul pulsante Reload (Ricarica).
  • Riavviare il browser
  • Esegui npm cache clean dalla riga di comando.

Successivo

Scopri come scattare una foto, ottenere i dati dell'immagine e condividerli tra colleghi da remoto.

9. Scatta una foto e condividila tramite un canale di dati

Obiettivi didattici

In questo passaggio imparerai a:

  • Scatta una foto e acquisisci i dati utilizzando l'elemento canvas.
  • Scambiare dati di immagine con un utente remoto.

La versione completa di questo passaggio è disponibile nella cartella step-06.

Come funziona

In precedenza hai imparato a scambiare messaggi utilizzando RTCDataChannel.

Questo passaggio consente di condividere interi file: in questo esempio, le foto acquisite tramite getUserMedia().

I componenti principali di questo passaggio sono i seguenti:

  1. Stabilisci un canale di dati. Tieni presente che in questo passaggio non aggiungerai stream multimediali alla connessione peer.
  2. Acquisisci lo stream video della webcam dell'utente con 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. Quando l'utente fa clic sul pulsante Aggancia, acquisisci un'istantanea (un fotogramma video) dallo stream video e visualizzala in un elemento 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. Quando l'utente fa clic sul pulsante Invia, converti l'immagine in byte e inviali tramite un canale dati:
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. Il lato ricevente converte i byte dei messaggi del canale dati in un'immagine e mostra l'immagine all'utente:
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);
}

Ottieni il codice

Sostituisci i contenuti della cartella work con i contenuti di step-06. Ora il tuo file index.html nel file work dovrebbe avere il seguente aspetto**:**

<!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>

Se non stai seguendo questo codelab dalla tua directory work, potresti dover installare le dipendenze per la cartella step-06 o la tua cartella di lavoro attuale. Esegui il comando seguente dalla directory di lavoro:

npm install

Una volta installato, se il tuo server Node.js non è in esecuzione, avvialo richiamando il seguente comando dalla tua directory work:

node index.js

Assicurati di utilizzare la versione di index.js che implementa Socket.IO e ricorda di riavviare il server Node.js se apporti modifiche. Per ulteriori informazioni su I/O di nodi e socket, consulta la sezione "Configurare un servizio di segnalazione per lo scambio di messaggi".

Se necessario, fai clic sul pulsante Consenti per consentire all'app di utilizzare la webcam.

L'app creerà un ID stanza casuale e lo aggiungerà all'URL. Apri l'URL dalla barra degli indirizzi in una nuova scheda o finestra del browser.

Fai clic sul pulsante Allinea e Invia, quindi guarda l'area In arrivo nell'altra scheda in fondo alla pagina. L'app trasferisce le foto tra le schede.

Il risultato dovrebbe essere simile a questo:

911b40f36ba6ba8.png

Punti bonus

  1. Come si può modificare il codice per consentire la condivisione di qualsiasi tipo di file?

Scopri di più

Che cosa hai imparato

  • Come scattare una foto e ricavarne i dati utilizzando l'elemento canvas.
  • Come scambiare questi dati con un utente remoto.

La versione completa di questo passaggio è disponibile nella cartella step-06.

10. Complimenti

Hai creato un'app per trasmettere video in streaming e scambiare dati in tempo reale.

Che cosa hai imparato

In questo codelab hai imparato a:

  • Riprendi il video dalla tua webcam.
  • Trasmetti video in streaming con RTCPeerConnection.
  • Trasmetti il flusso di dati con RTCDataChannel.
  • Configura un servizio di segnalazione per lo scambio di messaggi.
  • Combinare la connessione peer e l'indicatore.
  • Scatta una foto e condividila tramite un canale di dati.

Passaggi successivi

Scopri di più

  • Sul sito webrtc.org è disponibile una serie di risorse per iniziare a utilizzare WebRTC.