Kommunikation in Echtzeit mit WebRTC aktivieren

1. Hinweis

In diesem Codelab lernen Sie, wie Sie eine App erstellen, um mit Ihrer Webcam Videos zu erstellen, Screenshots zu erstellen und sie mit WebRTC zu teilen. Außerdem erfahren Sie, wie Sie die wichtigsten WebRTC-APIs verwenden und einen Nachrichtenserver mit Node.js einrichten.

Vorbereitung

  • Grundkenntnisse in HTML, CSS und JavaScript

Aufgaben

  • Video von deiner Webcam laden.
  • Streamen Sie Videos mit RTCPeerConnection.
  • Mit RTCDataChannel können Sie Daten streamen.
  • Signalisierungsdienst für den Austausch von Nachrichten einrichten
  • Kombinieren Sie Peer-Verbindungen und Signale.
  • Nehmen Sie ein Foto auf und teilen Sie es über einen Datenkanal.

Voraussetzungen

  • Chrome 47 oder höher
  • Webserver für Chrome oder einen Webserver Ihrer Wahl
  • Einen Texteditor Ihrer Wahl
  • Node.js

2. Beispielcode abrufen

Code herunterladen

  1. Wenn Sie mit Git vertraut sind, führen Sie folgenden Befehl aus, um den Code für dieses Codelab aus GitHub zu klonen:
git clone https://github.com/googlecodelabs/webrtc-web

Sie können auch auf diesen Link klicken, um eine ZIP-Datei des Codes herunterzuladen:

  1. Öffnen Sie die heruntergeladene ZIP-Datei, um einen Projektordner mit dem Namen webrtc-web-master zu entpacken. Dieser Ordner enthält für jeden Schritt dieses Codelabs einen Ordner und alle benötigten Ressourcen.

Sie ausführen den gesamten Code im Verzeichnis work.

Die Ordner step-nn enthalten für jeden Schritt dieses Codelabs eine abgeschlossene Version. als Referenz.

Webserver installieren und bestätigen

Sie können zwar Ihren eigenen Webserver verwenden, dieses Codelab ist aber so konzipiert, dass es sich gut für den Webserver für Chrome eignet.

  1. Wenn Sie keinen Webserver für Chrome haben, klicken Sie auf diesen Link, um ihn aus dem Chrome Web Store zu installieren:

d0a4649b4920cf3.png

  1. Klicken Sie auf Hinzufügen. Dadurch wird Webserver für Chrome installiert und Ihre Google-Apps automatisch in einem neuen Tab geöffnet.
  2. Klicken Sie auf Webserver:

27fce4494f641883.png

Daraufhin öffnet sich ein Dialogfeld, in dem Sie Ihren lokalen Webserver konfigurieren können:

a300381a486b9e22.png

  1. Klicken Sie auf Ordner auswählen.
  2. Wähle den von dir erstellten Ordner work aus.

Unter Webserver-URLs wird die URL angezeigt, unter der Sie Ihre aktuellen Werke abrufen können.

Chrome.

  1. Klicken Sie unter Optionen (möglicherweise Neustart) das Kästchen Index.html automatisch anzeigen an.
  2. Schalten Sie Web Server: Starten zweimal aus, um den Server anzuhalten und neu zu starten.

f23cafb3993dfac1.png

  1. Klicken Sie auf die URL unter Webserver-URLs, um Ihre Arbeit in Ihrem Webbrowser anzusehen.

Es sollte folgende Seite angezeigt werden, die work/index.html entspricht:

18a705cb6webstore5181.png

Natürlich ist diese App noch nicht sehr ansprechend. Es ist nur ein kleines Skelett, um sicherzustellen, dass Ihr Webserver ordnungsgemäß funktioniert. Sie fügen Funktionen und Layoutfunktionen in den nachfolgenden Schritten hinzu.

3. Video von Ihrer Webcam streamen

Eine vollständige Version dieses Schritts befindet sich im Ordner step-01.

Bindestrich von HTML hinzufügen

Kopiere diesen Code und füge ihn in der Datei index.html in deinem work-Verzeichnis ein, um ein video- und ein script-Element hinzuzufügen:

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

Fügen Sie JavaScript-Code hinzu

Kopiere diesen Code und füge ihn in der Datei main.js in den Ordner js ein:

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

Jetzt testen

Öffnen Sie die Datei index.html in Ihrem Browser. Die Ansicht sollte von Ihrer Webcam stammen:

9297048e43ed0f3d.png

Funktionsweise

Nach dem getUserMedia()-Aufruf fordert der Browser die Berechtigung zum Zugriff auf Ihre Kamera an, sofern es sich um die erste Anfrage nach Kamerazugriff für den aktuellen Ursprung handelt.

Wenn der Vorgang erfolgreich war, wird ein MediaStream-Objekt zurückgegeben, das ein media-Element über das srcObject-Attribut verwenden kann:

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


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

Mit dem Argument constraints können Sie angeben, welche Medien abgerufen werden sollen. In diesem Beispiel sind die Medien nur Videos, da die Audiowiedergabe standardmäßig deaktiviert ist:

const mediaStreamConstraints = {
  video: true,
};

Damit können Sie weitere Anforderungen erfüllen, etwa die Videoauflösung:

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

In der MediaTrackConstraints-Spezifikation werden alle möglichen Einschränkungstypen aufgelistet, obwohl nicht alle Optionen von allen Browsern unterstützt werden. Wenn die angeforderte Auflösung von der aktuell ausgewählten Kamera nicht unterstützt wird, wird getUserMedia() mit einem OverconstrainedError abgelehnt und du wirst aufgefordert, den Zugriff auf deine Kamera zu erlauben.

Wenn getUserMedia() erfolgreich ist, wird der Videostream aus der Webcam als Quelle des Videoelements festgelegt:

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

Bonuspunkte sammeln

  • Das an getUserMedia() übergebene localStream-Objekt befindet sich im globalen Bereich, sodass Sie es in der Browserkonsole prüfen können. Öffnen Sie die Konsole, geben Sie stream, ein und drücken Sie Enter (Return auf dem Mac). Falls Sie die Konsole in Chrome ansehen möchten, drücken Sie Control+Shift+J (oder Command+Option+J auf dem Mac).
  • Was gibt localStream.getVideoTracks() zurück?
  • Rufen Sie einfach localStream.getVideoTracks()[0].stop() an.
  • Sehen Sie sich das Beschränkungensobjekt an. Was passiert, wenn du die Einstellung zu „{audio: true, video: true}“ änderst?
  • Welche Größe hat das Videoelement? Wie kannst du die natürliche Größe des Videos im Gegensatz zur Anzeigegröße aus JavaScript abrufen? Das können Sie mit den Google Chrome-Entwicklertools tun.
  • Fügen Sie dem Videoelement CSS-Filter hinzu:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Fügen Sie SVG-Filter so hinzu:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Tipps

Best Practice

Achten Sie darauf, dass das Videoelement nicht in den Container überläuft. In diesem Codelab wurden width und max-width hinzugefügt, um eine bevorzugte Größe und eine maximale Größe für das Video festzulegen. Die Höhe wird von Ihrem Browser automatisch berechnet.

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

4. Videos mit der RTCPeerConnection API streamen

Eine vollständige Version dieses Schritts befindet sich im Ordner step-2.

Videoelemente und Steuerungsschaltflächen hinzufügen

Ersetze in der Datei index.html das einzelne video-Element durch zwei video- und drei button-Elemente:

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

Ein Videoelement zeigt den Stream von getUserMedia() an und das andere das RTCPeerconnection. In einer echten App würde ein video-Element den lokalen Stream und das andere den Remote-Stream anzeigen.

Adapter „Adapter.js“ hinzufügen

Kopieren Sie dieses Skriptelement und fügen Sie es über dem Skriptelement für main.js ein:

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

Ihre index.html-Datei sollte dann so aussehen:

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

RTCPeerConnection-Code installieren

Ersetzen Sie main.js durch die Version im Ordner step-02.

Anrufen

  1. Öffnen Sie die Datei index.html.
  2. Klicke auf Starten, um das Video von deiner Webcam abzurufen.
  3. Klicken Sie auf Anrufen, um eine Peer-Verbindung herzustellen.

In beiden video-Elementen solltest du dasselbe Video von deiner Webcam sehen.

  1. Rufen Sie die Browserkonsole auf, um die WebRTC-Protokollierung zu sehen.

Funktionsweise

Dieser Schritt ist viel machbar.

WebRTC verwendet die RTCPeerConnection API, um eine Verbindung zum Streamen von Videos zwischen WebRTC-Clients, sogenannten Peers, einzurichten.

In diesem Beispiel befinden sich die beiden RTCPeerConnection-Objekte auf derselben Seite: pc1 und pc2.

Die Anrufeinrichtung zwischen WebRTC-Peers umfasst drei Aufgaben:

  1. Erstelle für jedes Ende des Aufrufs einen RTCPeerConnection und füge an jedem Ende den lokalen Stream von getUserMedia() hinzu.
  2. Netzwerkinformationen abrufen und teilen.

Potenzielle Verbindungsendpunkte werden als ICE-Kandidaten bezeichnet.

  1. Lokale und Remote-Beschreibungen abrufen und teilen

Metadaten zu lokalen Medien werden im Sitzungsbeschreibungsprotokoll (SDP) angezeigt.

Stellen Sie sich vor, dass Alice und Tom mit RTCPeerConnection einen Videochat einrichten möchten.

Zuerst tauschen Alice und Bob Netzwerkinformationen aus. Der Ausdruck Ergebniskandidaten bezieht sich auf die Suche nach Netzwerkschnittstellen und Ports mit dem ICE-Framework.

  1. Alice erstellt ein RTCPeerConnection-Objekt mit einem onicecandidate (addEventListener('icecandidate'))-Handler.

Das entspricht dem folgenden Code von main.js:

let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Alice ruft getUserMedia() auf und fügt den ihr übergebenen Stream hinzu:
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. Der onicecandidate-Handler aus dem ersten Schritt wird aufgerufen, wenn Netzwerkkandidaten verfügbar sind.
  2. Alice sendet serialisierte Kandidatendaten an Robert.

In einer echten App erfolgt dieser Vorgang über ein Messaging-System. Wie das geht, erfahren Sie in einem späteren Schritt. In diesem Schritt befinden sich die beiden RTCPeerConnection-Objekte natürlich auf derselben Seite und können direkt ohne externe Messaging kommunizieren.

  1. Wenn Tom eine Nachricht über den Kandidaten von Alice erhält, ruft er addIceCandidate() auf, um den Kandidaten der Remote-Peer-Beschreibung hinzuzufügen:
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}.`);
  }
}

WebRTC-Peers müssen lokale und Remote-Audio- und Videomedieninformationen, wie Auflösung und Codec, ebenfalls ermitteln und austauschen. Die Signalisierung beim Austausch von Informationen zur Mediakonfiguration wird mit dem Austausch von Blobs aus Metadaten, einem Angebot und einer Antwort, im SDP-Format fortgesetzt.

  1. Alice führt die RTCPeerConnection createOffer()-Methode aus.

Das Versprechen enthält eine lokale RTCSessionDescription-Sitzungsbeschreibung:

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. Wenn die Anfrage erfolgreich ist, legt Alice die lokale Beschreibung mit setLocalDescription() fest und sendet sie dann über ihren Signalisierungskanal an Bob.
  2. Bob legt setRemoteDescription() mit der Beschreibung als Remote-Beschreibung fest.
  3. Bodo führt die Methode RTCPeerConnection createAnswer() aus und übergibt sie die Remotebeschreibung, die er von Alice erhalten hat. Dadurch wird eine lokale Sitzung generiert, die mit ihrer kompatibel ist.
  4. Das Promise createAnswer() übergibt einen RTCSessionDescription, den Bob als lokale Beschreibung festlegt und an Alice sendet.
  5. Wenn Alice die Sitzungsbeschreibung erhält, legt sie sie mit setRemoteDescription() als Remote-Beschreibung fest.
// 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);
}

Bonuspunkte sammeln

  1. Gehen Sie zu chrome://webrtc-internals.

Diese Seite enthält WebRTC-Statistiken und Debugging-Daten. Eine vollständige Liste der Chrome-URLs finden Sie unter chrome://about.

  1. Gestalten Sie die Seite mit CSS:
  2. Videos nebeneinander platzieren
  3. Schaltflächen in der Textgröße sind größer.
  4. Prüfen Sie, ob das Layout auf Mobilgeräten funktioniert.
  5. Sehen Sie sich in der Chrome-Entwicklertools-Konsole localStream, localPeerConnection und remotePeerConnection an.
  6. Sehen Sie sich in der Konsole localPeerConnectionpc1.localDescription an.

Wie sieht das SDP-Format aus?

Tipps

  • Weitere Informationen zum Shim-Adapter „Adapter.js“ finden Sie im GitHub-Repository unter adapter.js.
  • Sehen Sie sich AppRTC und seinen Code an, die kanonische App für WebRTC-Aufrufe. Anrufeinrichtung dauert weniger als 500 ms.

Best Practice

Wenn Sie Ihren Code zukunftssicher machen möchten, verwenden Sie die neuen Promise-basierten APIs. Außerdem wird die Kompatibilität mit Browsern ermöglicht, die sie mit adapter.js unterstützen.

5. Daten über den Datenkanal austauschen

Eine vollständige Version dieses Schritts befindet sich im Ordner step-03.

HTML aktualisieren

In diesem Schritt verwenden Sie WebRTC-Datenkanäle, um Text zwischen zwei textarea-Elementen auf derselben Seite zu senden. Das ist nicht sehr nützlich, zeigt jedoch, wie WebRTC zum Teilen von Daten und zum Streamen von Videos verwendet werden kann.

Entfernen Sie die Elemente video und button aus index.html, und ersetzen Sie sie durch folgenden HTML-Code:

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

Eine textarea dient zur Texteingabe, die andere zum Streamen von Texten aus anderen Apps.

Ihre index.html-Datei sollte dann so aussehen:

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

JavaScript aktualisieren

  1. Ersetze main.js durch den Inhalt von step-03/js/main.js.
  1. Versuchen Sie, Daten zwischen Peers zu streamen:
  2. Öffnen Sie index.html.
  3. Klicken Sie auf Start, um die Peer-Verbindung einzurichten.
  4. Geben Sie links in das Feld textarea einen Text ein.
  5. Klicken Sie auf Senden, um den Text über einen WebRTC-Datenkanal zu übertragen.

Funktionsweise

Dieser Code verwendet RTCPeerConnection und RTCDataChannel, um SMS auszutauschen.

Der Großteil des Codes in diesem Schritt ist mit dem Beispiel RTCPeerConnection identisch. Die Funktionen sendData() und createConnection() haben den Großteil des neuen Codes:

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

Die Syntax von RTCDataChannel ähnelt WebSocket mit einer send()-Methode und einem message-Ereignis.

Sie sollten dataConstraint verwenden. Datenkanäle können konfiguriert werden, um verschiedene Arten der Datenfreigabe zu ermöglichen, z. B. die zuverlässige Auslieferung gegenüber einer guten Leistung.

Bonuspunkte sammeln

  1. Bei SCTP ist das von WebRTC-Datenkanälen verwendete Protokoll standardmäßig die zuverlässige und geordnete Datenübermittlung aktiviert. Wann kann RTCDataChannel für eine zuverlässige Datenübermittlung sorgen und wann ist die Leistung möglicherweise wichtiger, auch wenn dies dazu führt, dass Daten verloren gehen?
  2. Nutzen Sie CSS, um das Seitenlayout zu verbessern und einem dataChannelReceive-textarea ein Platzhalterattribut hinzuzufügen.
  3. Testen Sie die Seite auf einem Mobilgerät.

Weitere Informationen

6. Signaldienst für den Austausch von Nachrichten einrichten

Jetzt weißt du, wie Daten auf derselben Seite zwischen verschiedenen Peers ausgetauscht werden. Aber wie geht das bei verschiedenen Rechnern? Als Erstes müssen Sie einen Signalkanal für den Austausch von Metadatennachrichten einrichten.

Eine vollständige Version dieses Schritts befindet sich im Ordner step-04.

Die App

WebRTC verwendet eine clientseitige JavaScript API, aber in der Praxis wird außerdem ein Signaling-Server (Messaging-Server) sowie ein STUN- und TURN-Server benötigt. Weitere Informationen

In diesem Schritt erstellen Sie einen einfachen Node.js-Signalserver mit dem Socket.IO-Node.js-Modul und der JavaScript-Bibliothek für Messaging.

In diesem Beispiel wird der Server (die Node.js-App) in index.js implementiert und der Client, der darauf ausgeführt wird (die Webanwendung), in index.html implementiert.

Die Node.js-Anwendung in diesem Schritt hat zwei Aufgaben.

Zuerst fungiert sie als Relay für Nachrichten:

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

Danach verwaltet sie WebRTC-Videochats:

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

In Ihrer einfachen WebRTC-Anwendung können maximal zwei Peers einen Raum teilen.

HTML und JavaScript

  1. Aktualisieren Sie index.html so:
<!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>

In diesem Schritt wird auf der Seite nichts angezeigt. Die gesamte Protokollierung erfolgt in der Browserkonsole. Falls Sie die Konsole in Chrome ansehen möchten, drücken Sie Control+Shift+J (oder Command+Option+J auf dem Mac).

  1. Ersetzen Sie js/main.js durch Folgendes:
'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);
});

Socket.IO-Datei für die Ausführung auf Node.js einrichten

Vielleicht hast du in der HTML-Datei schon festgestellt, dass du eine Socket.IO-Datei verwendest:

<script src="/socket.io/socket.io.js"></script>
  1. Erstellen Sie auf der obersten Ebene Ihres work-Verzeichnisses eine Datei mit dem Namen package.json und dem folgenden Inhalt:
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Dies ist ein App-Manifest, das Node Package Manager (npm) mitteilt, welches Projekt

Abhängigkeiten zu installieren.

  1. Führe die folgenden Schritte über das Befehlszeilentool in deinem work-Verzeichnis aus, um Abhängigkeiten wie /socket.io/socket.io.js zu installieren:
npm install

Es sollte ein Installationsprotokoll angezeigt werden, das ungefähr so endet:

3ab06b7bcc7664b9

Wie Sie sehen, hat npm die in package.json definierten Abhängigkeiten installiert.

  1. Erstellen Sie eine neue Datei index.js auf der obersten Ebene Ihres work-Verzeichnisses (nicht des Verzeichnisses js) und fügen Sie den folgenden Code hinzu:
'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. Führen Sie im Befehlszeilentool den folgenden Befehl im Verzeichnis work aus:
node index.js
  1. Rufen Sie in einem Browser http://localhost:8080 auf.

Jedes Mal, wenn Sie diese URL aufrufen, werden Sie aufgefordert, einen Namen für den Chatroom einzugeben.

Geben Sie für denselben Raum jedes Mal denselben Namen ein, z. B. foo.

  1. Öffnen Sie einen neuen Tab und gehen Sie zu http://localhost:8080. Geben Sie den Raumnamen noch einmal ein.
  2. Öffnen Sie einen weiteren neuen Tab, gehen Sie zu http://localhost:8080 und geben Sie denselben Raumnamen noch einmal ein.
  3. Prüfen Sie die Konsole in den einzelnen Tabs.

Es sollte eine Protokollierung aus JavaScript angezeigt werden.

Bonuspunkte sammeln

  • Welche alternativen Mechanismen für Werbebotschaften sind möglich? Welche Probleme können bei der Verwendung von purer WebSocket auftreten?
  • Welche Probleme könnten mit der Skalierung dieser Anwendung verbunden sein? Entwickeln Sie eine Methode zum Testen Tausender oder Millionen gleichzeitiger Raumanfragen?
  • Diese App verwendet eine JavaScript-Aufforderung, um einen Raumnamen abzurufen. Herausfinden, wie der Zimmername aus der URL abgerufen wird Beispiel: http://localhost:8080/foo würde den Raumnamen geben. foo.

Weitere Informationen

7. Peer-Verbindung und Signalierung kombinieren

Eine vollständige Version dieses Schritts befindet sich im Ordner step-05.

HTML und JavaScript ersetzen

  1. Ersetzen Sie den Inhalt von index.html durch Folgendes:
<!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. Ersetze js/main.js durch den Inhalt von step-05/js/main.js.

Node.js-Server ausführen

Wenn du diesem Codelab nicht aus deinem work-Verzeichnis folgst, musst du möglicherweise die Abhängigkeiten für den Ordner step-05 oder deinen aktuellen Arbeitsordner installieren.

  1. Führen Sie in Ihrem Arbeitsverzeichnis den folgenden Befehl aus:
npm install
  1. Wenn der Node.js-Server nach der Installation nicht ausgeführt wird, führen Sie den folgenden Befehl im Verzeichnis work aus:
node index.js

Achten Sie darauf, dass Sie die Version von index.js aus dem vorherigen Schritt verwenden, mit dem Socket.IO implementiert wird. Weitere Informationen zu Knoten und Socket IO finden Sie im Abschnitt „Signalisierungsdienst für den Austausch von Nachrichten einrichten“.

  1. Gehen Sie in Ihrem Browser zu http://localhost:8080.
  2. Öffnen Sie einen neuen Tab und gehen Sie noch einmal zu http://localhost:8080.

Ein video-Element enthält den lokalen Stream von getUserMedia() und das andere das Remote-Video, das über RTCPeerconnection gestreamt wurde.

  1. Sehen Sie sich die Protokollierung in der Browserkonsole an.

b****onus-Punkte sammeln

  • Diese App unterstützt nur Videoanrufe. Wie können Sie das Design ändern, damit mehrere Personen sich einen Raum für einen Videoanruf teilen können?
  • Im Beispiel ist der Zimmername foo hartcodiert. Wie lassen sich andere Raumnamen am besten aktivieren?
  • Wie würden Nutzer den Namen des Chatrooms teilen? Erstellen Sie eine Alternative zur Freigabe von Raumnamen.
  • Wie könnten Sie die App ändern?

Tipps

  • WebRTC-Statistiken finden und Fehler in Daten unter chrome://webrtc-internals beheben.
  • Prüfen Sie mithilfe der WebRTC-Fehlerbehebung Ihre lokale Umgebung sowie die Kamera und Mikrofon.
  • Wenn Sie Probleme mit dem Caching haben, versuchen Sie Folgendes:
  1. Drücken Sie Control und klicken Sie auf Diese Seite aktualisieren.
  2. Starten Sie den Browser neu.
  3. Führen Sie npm cache clean über die Befehlszeile aus.

8. Foto aufnehmen und über einen Datenkanal teilen

Eine vollständige Version dieses Schritts befindet sich im Ordner step-06.

Funktionsweise

Bisher hast du gelernt, wie du mit RTCDataChannel SMS austauschen kannst. Dieser Schritt ermöglicht es, ganze Dateien freizugeben. In diesem Beispiel werden Fotos mit getUserMedia() aufgenommen.

Die wichtigsten Schritte dieses Schritts sind:

  1. Richten Sie einen Datenkanal ein.

In diesem Schritt fügen Sie der Peer-Verbindung keine Medienstreams hinzu.

  1. Webcam-Videostream mit getUserMedia() aufnehmen:
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. Klicken Sie auf Snap, um einen Snapshot (Videoframe) aus dem Videostream zu erhalten und in einem canvas-Element anzuzeigen:
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. Klicken Sie auf Senden, um das Bild in Byte zu konvertieren und über einen Datenkanal zu senden:
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));
  }
}

Die Empfängerseite wandelt Bytes der Datenkanalnachricht in ein Bild um und zeigt es dem Nutzer an:

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

Code abrufen

  1. Ersetze den Inhalt deines Ordners work durch den Inhalt von „step-06“.

Ihre index.html-Datei in work sollte jetzt so aussehen:**

<!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. Wenn du diesem Codelab nicht aus deinem work-Verzeichnis folgst, musst du möglicherweise die Abhängigkeiten für den Ordner step-06 oder deinen aktuellen Arbeitsordner installieren. Führen Sie einfach folgenden Befehl in Ihrem Arbeitsverzeichnis aus:
npm install
  1. Wenn Ihr Node.js-Server nach der Installation nicht ausgeführt wird, führen Sie den folgenden Befehl in Ihrem work-Verzeichnis aus:
node index.js
    Make sure that you're using the version of `index.js` that implements Socket.IO and 

dass Sie Ihren Node.js-Server neu starten müssen, wenn Sie Änderungen vornehmen.

Weitere Informationen zu Node.com und Socket.IO finden Sie im Abschnitt "Signalisierung einrichten".

um Nachrichten auszutauschen.

  1. Klicken Sie gegebenenfalls auf Zulassen, damit die App Ihre Webcam verwenden kann.

Die App erstellt eine zufällige Raum-ID und fügt sie der URL hinzu.

  1. Öffnen Sie die URL in einem neuen Browsertab oder Fenster in der Adressleiste.
  2. Klicken Sie auf Andocken und senden und sehen Sie sich dann auf dem anderen Tab unten auf der Seite Eingehende Fotos an.

Die App überträgt Fotos zwischen Tabs.

Auf dem Bildschirm sollte Folgendes zu sehen sein:

911b40f36ba6ba8.png

Bonuspunkte sammeln

Wie können Sie den Code ändern, damit jeder Dateityp freigegeben werden kann?

Weitere Informationen

9. Glückwunsch

Sie haben eine App für Videostreaming und Datenaustausch in Echtzeit entwickelt.

Weitere Informationen