Abilita la comunicazione in tempo reale con WebRTC

1. Prima di iniziare

Questo codelab ti insegna come creare un'app che consenta di acquisire video e scattare istantanee con la webcam, nonché di condividerli peer-to-peer con WebRTC. Imparerai inoltre a utilizzare le API WebRTC principali e a configurare un server di messaggistica con Node.js.

Prerequisiti

  • Conoscenza di base di HTML, CSS e JavaScript

Cosa devi creare

  • Ottieni i video dalla webcam.
  • Riproduci il video in streaming con RTCPeerConnection.
  • Trasmetti dati con RTCDataChannel.
  • Configura un servizio di segnalazione per lo scambio di messaggi.
  • Combina la connessione e la segnalazione peer.
  • Scatta una foto e utilizza un canale dati per condividerlo.

Che cosa ti serve

  • Chrome 47 o versioni successive
  • Server web per Chrome o un server web a tua scelta
  • Un editor di testo a tua scelta
  • Node.js

2. Recupera il codice campione

Scarica il codice

  1. Se hai familiarità con Git, esegui questo comando per clonare il codice per questo codelab da GitHub:
git clone https://github.com/googlecodelabs/webrtc-web

In alternativa, fai clic su questo link per scaricare un file ZIP del codice:

  1. Apri il file ZIP scaricato per decomprimere una cartella di progetto denominata webrtc-web-master, che contiene una cartella per ogni passaggio di questo codelab e tutte le risorse necessarie.

Tutto il codice funziona nella directory denominata work.

Le cartelle step-nn contengono una versione terminata per ogni passaggio di questo codelab. Sono a tua disposizione.

Installare e verificare il server web

Puoi utilizzare il tuo server web, ma questo codelab è progettato per funzionare bene con Web Server per Chrome.

  1. Se non hai il server web per Chrome, fai clic su questo link per installarlo dal Chrome Web Store:

d0a4649b4920cf3.png

  1. Fai clic su Aggiungi a Chrome, che installa Web Server per Chrome e apre automaticamente le tue app Google in una nuova scheda.
  2. Fai clic su Web Server:

27fce4494f641883.png

Viene visualizzata una finestra di dialogo che ti consente di configurare il server web locale:

a300381a486b9e22.png

  1. Fai clic su Scegli cartella.
  2. Seleziona la cartella work che hai creato.

Nella sezione URL server web, vedrai l'URL in cui puoi visualizzare il lavoro in corso.

Chrome

  1. Nella sezione Opzioni (potrebbe essere necessario riavviare), seleziona la casella di controllo Mostra automaticamente index.html.
  2. Attiva/disattiva Server web: avviato due volte per interrompere e riavviare il server.

f23cafb3993dfac1.png

  1. Fai clic sull'URL sotto URL server web per visualizzare la tua opera nel browser web.

Dovresti vedere una pagina simile alla seguente, corrispondente a work/index.html:

18a705cb6ccc5181.png

Ovviamente, questa app non sta ancora facendo nulla di interessante. È solo uno scheletro minimo per assicurare che il tuo server web funzioni correttamente. Potrai aggiungere funzionalità e layout alle fasi successive.

3. Riprodurre video in streaming dalla webcam

Una versione completa di questo passaggio è disponibile nella cartella step-01.

Aggiungi un trattino HTML

Copia questo codice e incollalo nel file index.html nella tua directory work per aggiungere un elemento video e script:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

  <video autoplay playsinline></video>

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

</body>

</html>

Aggiungi un pizzico di JavaScript

Copia questo codice e incollalo nel file main.js della cartella js:

'use strict';

// In this codelab, you  only stream video, not audio (video: true).
const mediaStreamConstraints = {
  video: true,
};

// The video element where the stream is displayed
const localVideo = document.querySelector('video');

// The local stream that's displayed on the video
let localStream;

// Handle success and add the MediaStream to the video element
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

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

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

Prova

Apri il file index.html nel browser e dovresti vedere qualcosa di simile, ma con la visualizzazione dalla webcam:

9297048e43ed0f3d.png

Come funziona

Dopo la chiamata getUserMedia(), il browser richiede l'autorizzazione ad accedere alla tua videocamera se è la prima richiesta di accesso alla videocamera per l'origine attuale.

In caso di esito positivo, viene restituito un MediaStream, che può essere utilizzato da un elemento media tramite l'attributo srcObject:

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


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

L'argomento constraints consente di specificare i contenuti multimediali da ricevere. In questo esempio, i contenuti multimediali sono solo video perché l'audio è disattivato per impostazione predefinita:

const mediaStreamConstraints = {
  video: true,
};

Puoi utilizzare i vincoli per requisiti aggiuntivi, ad esempio la risoluzione dei video:

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

La specifica MediaTrackConstraints elenca tutti i potenziali tipi di vincolo, anche se non tutte le opzioni sono supportate da tutti i browser. Se la risoluzione richiesta non è supportata dalla fotocamera attualmente selezionata, getUserMedia() viene rifiutata se viene usata la fotocamera OverconstrainedError e ti viene chiesto di autorizzare l'accesso alla fotocamera.

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

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

Guadagna punti bonus

  • L'oggetto localStream passato a getUserMedia() è nell'ambito globale, quindi puoi controllarlo dalla console del browser. Apri la console, digita stream, e premi Enter (Return su Mac). Per visualizzare la console in Chrome, premi Control+Shift+J (o Command+Option+J su Mac).
  • Che cosa restituisce localStream.getVideoTracks()?
  • Chiama localStream.getVideoTracks()[0].stop().
  • Osserva l'oggetto "vincoli". Che cosa succede quando la modifichi in {audio: true, video: true}?
  • Quali sono le dimensioni dell'elemento video? Come puoi ottenere le dimensioni naturali del video da JavaScript, anziché le dimensioni di visualizzazione? Utilizza gli Strumenti per sviluppatori di Google Chrome per controllare.
  • Aggiungi filtri CSS all'elemento video, in questo modo:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Aggiungi filtri SVG, come segue:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Suggerimenti

Best practice

Assicurati che l'elemento video non superi il relativo contenitore. Questo codelab ha aggiunto width e max-width per impostare una dimensione preferita e una dimensione massima per il video. Il browser calcola automaticamente l'altezza.

video {
  max-width: 100%;
  width: 320px;
}

4. Riprodurre video in streaming con l'API RTCPeerConnection

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

Aggiungere elementi video e pulsanti di controllo

Nel file index.html, sostituisci il singolo elemento video con due elementi video e tre button:

<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 mostra lo stream di getUserMedia(), mentre l'altro mostra lo stesso video trasmesso in streaming tramite RTCPeerconnection. In un'app reale, un elemento video mostrerà lo stream locale e l'altro lo stream remoto.

Aggiungere lo shim adattatore.js

Copia questo elemento dello script e incollalo sopra l'elemento di script per main.js:

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

Il tuo file index.html dovrebbe avere il seguente aspetto:

<!DOCTYPE html>
<html>

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

<body>
  <h1>Real-time 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 di RTCPeerConnection

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

Chiama

  1. Apri il file index.html.
  2. Fai clic su Avvia per scaricare il video dalla webcam.
  3. Fai clic su Chiama per stabilire la connessione peer.

Dovresti vedere lo stesso video nella webcam in entrambi gli elementi video.

  1. Visualizza la console del browser per vedere il logging WebRTC.

Come funziona

Questo passaggio è molto importante.

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

In questo esempio, i due oggetti RTCPeerConnection si trovano nella stessa pagina: pc1 e pc2.

La configurazione della chiamata tra peer WebRTC prevede tre attività:

  1. Crea un RTCPeerConnection per ogni fine della chiamata e, a ogni fine, aggiungi lo stream locale di getUserMedia().
  2. Ricevere e condividere informazioni sulla rete.

I potenziali endpoint di connessione sono noti come ICE.

  1. Ricevi e condividi descrizioni locali e remote.

I metadati sui contenuti multimediali locali sono in formato Protocollo descrizione sessione (SDP).

Immagina che Alice e Roberto vogliano utilizzare RTCPeerConnection per configurare una chat video.

Innanzitutto, Alice e Roberto si scambiano informazioni sulla rete. L'espressione ricerca di candidati si riferisce alla procedura di individuazione di interfacce e porte di rete che utilizza 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 il flusso trasmesso a questo livello:
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 primo passaggio viene chiamato quando diventano disponibili i candidati di rete.
  2. Alice invia i dati dei candidati serializzati a Roberto.

In un'app reale, questo processo, chiamato indicatore, avviene tramite un servizio di messaggistica. Imparerai come farlo in un passaggio successivo. Ovviamente, in questo passaggio, i due oggetti RTCPeerConnection si trovano sulla stessa pagina e possono comunicare direttamente, senza dover utilizzare messaggi esterni.

  1. Quando Roberto riceve un messaggio 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 inoltre scoprire e scambiare informazioni multimediali audio e video locali e remote, ad esempio funzionalità di risoluzione e codec. La segnalazione per lo scambio di informazioni di configurazione dei contenuti multimediali procede con lo scambio di blob di metadati, noti come offerta e risposta, utilizzando il formato SDP.

  1. Alice esegue il metodo createOffer() RTCPeerConnection.

La promessa restituita fornisce una descrizione della sessione locale di RTCSessionDescription-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() e poi invia la descrizione di questa sessione a Roberto tramite il canale di segnalazione.
  2. Roberto imposta la descrizione che Alice lo ha inviato come descrizione remota con setRemoteDescription().
  3. Roberto esegue il metodo createAnswer() RTCPeerConnection e lo trasmette alla descrizione remota che ha ricevuto da Alice, in modo che venga generata una sessione locale compatibile con la sua.
  4. La promessa createAnswer() supera un RTCSessionDescription, che Roberto imposta come descrizione locale e lo invia ad Alice.
  5. Quando Alice riceve la descrizione della sessione di Paolo, 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);
}

Guadagna punti bonus

  1. Vai a chrome://webrtc-internals.

Questa pagina fornisce statistiche WebRTC e dati di debug. Puoi trovare un elenco completo degli URL di Chrome all'indirizzo chrome://about.

  1. Definisci uno stile per la pagina con il CSS:
  2. Metti i video uno accanto all'altro.
  3. Imposta la stessa larghezza per i pulsanti con testo più grande.
  4. Assicurati che il layout funzioni sui dispositivi mobili.
  5. Nella console degli Strumenti per sviluppatori di Chrome, puoi trovare localStream, localPeerConnection e remotePeerConnection.
  6. Nella console, controlla localPeerConnectionpc1.localDescription.

Come si presenta il formato SDP?

Suggerimenti

  • Per ulteriori informazioni sullo shimadattatore.js, consulta adattatore.js repository GitHub.
  • Dai un'occhiata a AppRTC e al suo codice, l'app canonica del progetto WebRTC per le chiamate WebRTC. Il tempo di configurazione della chiamata è inferiore a 500 ms.

Best practice

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

5. Utilizzare un canale dati per lo scambio di dati

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

Aggiorna il codice HTML

Per questo passaggio devi utilizzare i canali dati WebRTC per inviare testo tra due elementi textarea sulla stessa pagina. Non è molto utile, ma dimostra come WebRTC può essere utilizzato per condividere dati e riprodurre video in streaming.

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

Uno textarea consente di inserire il testo, mentre l'altro mostra il testo trasmesso in streaming tra app peer.

Il tuo file index.html dovrebbe avere il seguente aspetto:

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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

  1. Sostituisci main.js con i contenuti di step-03/js/main.js.
  1. Prova lo streaming dei dati tra app peer:
  2. Apri index.html.
  3. Fai clic su Avvia per configurare la connessione peer.
  4. Inserisci del testo nel textarea a sinistra.
  5. Fai clic su Invia per trasferire il testo utilizzando un canale dati WebRTC.

Come funziona

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

Gran parte del codice in questo passaggio è uguale a quello dell'esempio RTCPeerConnection. Le funzioni sendData() e createConnection() hanno 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 è deliberatamente simile a WebSocket con un metodo send() e un evento message.

Nota l'utilizzo di dataConstraint. I canali di dati possono essere configurati per abilitare diversi tipi di condivisione dei dati, ad esempio dando la priorità alla pubblicazione affidabile rispetto al rendimento.

Guadagna punti bonus

  1. Con SCTP, il protocollo utilizzato dai canali di dati WebRTC, l'affidabilità e l'invio dei dati ordinato è attivo per impostazione predefinita. In quali casi RTCDataChannel potrebbe essere necessario fornire dati in modo affidabile e quando il rendimento potrebbe essere più importante, anche se ciò comporta la perdita di alcuni dati?
  2. Utilizza i CSS per migliorare il layout delle pagine e aggiungere un attributo segnaposto a dataChannelReceive textarea.
  3. Esegui il test della pagina su un dispositivo mobile.

Scopri di più

6. Configura un servizio di segnalazione per lo scambio di messaggi

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

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

Informazioni sull'app

WebRTC utilizza un'API JavaScript lato client, ma per l'utilizzo reale è necessario disporre anche di un server di messaggistica (messaggistica), nonché di server STUN e TURN. Puoi trovare ulteriori informazioni qui.

In questo passaggio devi creare un semplice server di segnalazione Node.js utilizzando il modulo Socket.IO Node.js e la libreria JavaScript per i messaggi.

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

L'app Node.js in questo passaggio ha due attività.

In primo luogo, funge da inoltro messaggi:

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

In secondo luogo, gestisce le stanze virtuali di 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 tua semplice app WebRTC consente a un massimo di due peer di condividere una stanza virtuale.

HTML e JavaScript

  1. Aggiorna index.html in modo che assuma il seguente aspetto:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time communication with WebRTC</h1>

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

</html>

Non vedrai nulla nella pagina in questo passaggio. Tutto il logging viene eseguito nella console del browser. Per visualizzare la console in Chrome, premi Control+Shift+J (o Command+Option+J su Mac).

  1. Sostituisci js/main.js con uno dei seguenti valori:
'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);
});

Configurare il file Socket.IO per l'esecuzione su Node.js

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

<script src="/socket.io/socket.io.js"></script>
  1. Al livello superiore della directory work, crea un file denominato package.json con il seguente contenuto:
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Questo è un manifest dell'app che indica a Node Package Manager (npm) quale progetto

dipendenze da installare.

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

  1. Crea un nuovo file index.js al primo livello della tua directory work (non la 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);
        }
      });
    }
  });

});
  1. Dal terminale a riga di comando, esegui il comando seguente nella directory work:
node index.js
  1. Nel browser, vai alla pagina http://localhost:8080.

Ogni volta che accedi a questo URL ti verrà chiesto di inserire un nome per la stanza virtuale.

Per partecipare alla stessa stanza, inserisci ogni volta lo stesso nome, ad esempio foo.

  1. Apri una nuova scheda e vai di nuovo a http://localhost:8080, quindi inserisci di nuovo lo stesso nome della stanza virtuale.
  2. Apri un'altra nuova scheda, vai di nuovo a http://localhost:8080 e inserisci di nuovo lo stesso nome della stanza.
  3. Controlla la console in ciascuna scheda.

Dovresti vedere il logging dal codice JavaScript.

Guadagna punti bonus

  • Quali possibili meccanismi di messaggistica alternativi potrebbero essere forniti? Quali problemi potresti riscontrare utilizzando WebSocket puro?
  • Quali problemi potrebbero essere interessati dalla scalabilità di questa app? Riesci a sviluppare un metodo per testare migliaia o milioni di richieste di camere simultanee?
  • Questa app utilizza un messaggio JavaScript per recuperare il nome di una stanza virtuale. Scopri come ottenere il nome della stanza virtuale nell'URL. Ad esempio, http://localhost:8080/foo assegna il nome alla stanza virtuale foo.

Scopri di più

7. Combinare connessione e segnale peer

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

Sostituisci HTML e JavaScript

  1. Sostituisci i contenuti di index.html come segue:
<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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>
  1. Sostituisci js/main.js con i contenuti di step-05/js/main.js.

Esegui il server Node.js

Se non segui questo codelab dalla directory work, potresti dover installare le dipendenze per la cartella step-05 o la cartella di lavoro corrente.

  1. Esegui il seguente comando dalla directory di lavoro:
npm install
  1. Dopo l'installazione, se il server Node.js non è in esecuzione, avvialo eseguendo il comando seguente nella directory work:
node index.js

Assicurati di utilizzare la versione di index.js del passaggio precedente che implementa Socket.IO. Per ulteriori informazioni sull'ordine di inserzione di Node e Socket, consulta la sezione Configurazione di un servizio di segnalazione per lo scambio di messaggi.

  1. Dal browser, vai all'indirizzo http://localhost:8080.
  2. Apri una nuova scheda e vai di nuovo a http://localhost:8080.

Un elemento video mostra lo stream locale di getUserMedia(), mentre l'altro mostra il video remoto trasmesso in streaming tramite RTCPeerconnection.

  1. Visualizza il logging nella console del browser.

Punti b****dei punti forti

  • Questa app supporta solo le chat video a due. Come puoi modificare il design per consentire a più di una persona di condividere la stessa stanza virtuale della chat video?
  • L'esempio ha il nome della stanza foo hardcoded. Qual è il modo migliore per attivare i nomi delle altre stanze?
  • In che modo gli utenti condividerebbero il nome della stanza virtuale? Prova a creare un'alternativa alla condivisione dei nomi delle stanze.
  • Come puoi cambiare l'app?

Suggerimenti

  • Puoi trovare statistiche WebRTC ed eseguire il debug dei dati all'indirizzo chrome://webrtc-internals.
  • Utilizza lo strumento per la risoluzione dei problemi WebRTC per controllare il tuo ambiente locale e testare la videocamera e il microfono.
  • In caso di problemi strani con la memorizzazione nella cache, prova a procedere nel seguente modo:
  1. Premi Control e fai clic su Ricarica la pagina.
  2. Riavvia il browser.
  3. Esegui npm cache clean dalla riga di comando.

8. Scattare una foto e condividerla con un canale dati

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

Come funziona

In precedenza hai imparato a scambiare SMS utilizzando RTCDataChannel. In questo passaggio è possibile condividere interi file. In questo esempio, le foto vengono acquisite con getUserMedia().

Le parti fondamentali di questo passaggio sono le seguenti:

  1. Definisci un canale dati.

In questo passaggio non devi aggiungere stream multimediali alla connessione peer.

  1. Acquisisci il video stream della webcam 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. Fai clic su Snap per ottenere un'istantanea (un frame video) dal video stream e visualizzarla 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. Fai clic su Invia per convertire l'immagine in byte e inviarla attraverso un canale dati:
function sendPhoto() {
  // Split the 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 approximately 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 applicable.
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}

Il lato ricevente converte i byte dei messaggi del canale dati in un'immagine e mostra tale 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');
  // The trail is the element that holds 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

  1. Sostituisci i contenuti della cartella work con i contenuti di step-06.

Il tuo file index.html in work dovrebbe avere il seguente aspetto**:**

<!DOCTYPE html>
<html>

<head>

  <title>Real-time communication with WebRTC</title>

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

</head>

<body>

  <h1>Real-time 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>
  1. Se non segui questo codelab dalla directory work, potresti dover installare le dipendenze per la cartella step-06 o la cartella di lavoro corrente. Esegui il seguente comando dalla directory di lavoro:
npm install
  1. Dopo l'installazione, se il server Node.js non è in esecuzione, avvialo eseguendo il comando seguente dalla directory work:
node index.js
    Make sure that you're using the version of `index.js` that implements Socket.IO and 

ricorda di riavviare il server Node.js se apporti modifiche.

Per ulteriori informazioni su nodo e Socket.IO, consulta la sezione Configurare un indicatore

per scambiare messaggi.

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

L'app crea un ID camera casuale e lo aggiunge all'URL.

  1. Apri l'URL dalla barra degli indirizzi in una nuova scheda o finestra del browser.
  2. Fai clic su Snap &AMP e seleziona Foto in arrivo nell'altra scheda in fondo alla pagina.

L'app trasferisce le foto da una scheda all'altra.

Il risultato dovrebbe essere simile a questo:

911b40f36ba6ba8.png

Guadagna punti bonus

Come puoi modificare il codice per consentire la condivisione di qualsiasi tipo di file?

Scopri di più

9. Complimenti

Hai creato un'app per lo streaming di video e lo scambio di dati in tempo reale.

Scopri di più