1. 程式人生 > >與WebRTC實時通訊

與WebRTC實時通訊

與WebRTC實時通訊

一、介紹

WebRTC是一個開源專案,可以在Web和本機應用程式中實現音訊,視訊和資料的實時通訊。WebRTC有幾個JavaScript API:
getUserMedia():捕獲音訊和視訊。
MediaRecorder:錄製音訊和視訊。
RTCPeerConnection:在使用者之間傳輸音訊和視訊。
RTCDataChannel:使用者之間的流資料。
在Firefox,Opera和桌面和Android上的Chrome中可以使用WebRTC。WebRTC也可用於iOS和Android上的本機應用程式。

什麼是signaling?
WebRTC使用RTCPeerConnection在瀏覽器之間傳遞流資料,但也需要一種協調通訊和傳送控制訊息的機制,這一過程稱為信令。WebRTC未指定信令方法和協議。本例將使用Socket.IO進行訊息傳遞。

什麼是STUN和TURN?
WebRTC旨在實現點對點工作,因此使用者可以通過最直接的路由進行連線。但是,WebRTC的構建是為了應對真實的網路:客戶端應用程式需要遍歷NAT閘道器和防火牆,並且在直接連線失敗的情況下,對等網路需要回退。作為此過程的一部分,WebRTC API使用STUN伺服器獲取計算機的IP地址,並使用TURN伺服器作為中繼伺服器,以防對等通訊失敗。

二、概述

構建應用程式以獲取視訊並使用網路攝像頭拍攝快照,並通過WebRTC進行點對點共享。在此過程中,將學習如何使用核心WebRTC API並使用Node.js設定訊息傳遞伺服器。

你將學到什麼
從網路攝像頭獲取視訊
使用RTCPeerConnection流式傳輸視訊
使用RTCDataChannel流式傳輸資料
建立信令服務以交換訊息
結合對等連線和信令
拍照並通過資料通道分享

你需要什麼
Chrome 47或以上
適用於Chrome的Web Server,或使用您自己選擇的Web伺服器。
示例程式碼
文字編輯器
HTML,CSS和JavaScript的基礎知識

三、獲取示例程式碼

下載程式碼
如果您熟悉git,可以通過克隆它從GitHub下載此codelab的程式碼:

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

或者單擊此處下載程式碼的.zip檔案。

開啟下載的zip檔案。這個專案資料夾包含每個步驟以及需要的所有資源。您將在名為work的目錄中完成所有編碼工作。

安裝並驗證Web伺服器


雖然您可以自由使用自己的Web伺服器,但此codelab可以與Chrome Web伺服器配合使用。如果您尚未安裝該應用,則可以從Chrome網上應用店安裝該應用。
在這裡插入圖片描述

安裝Web Server for Chrome應用程式後,單擊書籤欄,新標籤頁或應用啟動器中的Chrome應用程式快捷方式:
在這裡插入圖片描述

單擊Web Server圖示:
在這裡插入圖片描述

接下來,您將看到此對話方塊,它允許您配置本地Web伺服器:

在這裡插入圖片描述

單擊“ 選擇資料夾”按鈕,然後選擇剛剛建立的工作資料夾。這樣,您就可以通過“ Web伺服器URL”部分的“Web伺服器”對話方塊中突出顯示的URL檢視Chrome中正在進行的工作。

在選項下,選中自動顯示index.html旁邊的框,如下所示:

在這裡插入圖片描述

然後通過滑動標記為Web Server的切換來停止並重新啟動伺服器:STARTED向左,然後向右移動。

在這裡插入圖片描述

現在,通過單擊突出顯示的Web伺服器URL,在Web瀏覽器中訪問您的工作站點。你應該看到一個看起來像這樣的頁面,它對應於work / index.html:
在這裡插入圖片描述

從現在開始,應使用此Web伺服器設定執行所有測試和驗證,只需重新整理測試瀏覽器選項卡。

四、通過網路攝像頭流式傳輸視訊

在這一步中,您將瞭解如何:
從您的網路攝像頭獲取視訊流。
操縱流播放。
使用CSS和SVG來操縱視訊。

此步驟的完整版本位於step-01資料夾中。

在工作目錄中為index.html新增video元素和script元素:

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

以下內容新增到main.js在您的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);

這裡的所有JavaScript示例都用於’use strict’;避免常見的編碼問題。

在瀏覽器中開啟index.html,可以看到網路攝像頭的檢視。

在getUserMedia()響應之後,瀏覽器請求使用者訪問其攝像機的許可(如果這是第一次請求當前的攝像機訪問)。如果成功,則返回MediaStream,媒體元素可以通過該srcObject屬性使用它:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

該constraints引數允許您指定要獲取的媒體。在此示例中,僅限視訊,因為預設情況下禁用音訊:

const mediaStreamConstraints = {
  video: true,
};

您可以使用約束來滿足其他要求,例如視訊解析度:

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

如果getUserMedia()成功,則將來自網路攝像頭的視訊流設定為視訊元素的來源:

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

不要忘記元素的autoplay屬性video。沒有它,你只會看到一個幀!

五、使用RTCPeerConnection流式傳輸視訊

在這一步中,您將瞭解:

使用RTCPeerConnection API流式傳輸視訊。
控制媒體捕獲和流媒體。

此步驟的完整版本位於step-2資料夾中。

什麼是RTCPeerConnection?
RTCPeerConnection是用於進行WebRTC呼叫以流式傳輸視訊和音訊以及交換資料的API。

此示例在同一頁面上的兩個RTCPeerConnection物件(稱為對等方)之間建立連線。實用性不大,但有助於理解RTCPeerConnection的工作原理。

新增視訊元素和控制按鈕
在index.html中,用兩個視訊元素和三個按鈕替換單個視訊元素:

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

一個視訊元素將顯示getUserMedia()流,另一個將顯示通過RTCPeerconnection流傳輸的相同視訊。(在實際應用程式中,一個視訊元素將顯示本地流,另一個視訊元素將顯示遠端流。)

新增adapter.js填充程式
添加當前版本adapter.js 連結上面main.js:

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

adapter.js是一個填充程式,可以將應用程式與規範更改和字首差異隔離開來。
Index.html現在應該如下所示:

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

安裝RTCPeerConnection程式碼
將main.js替換為step-02資料夾中的版本。
在codelab中使用大塊程式碼進行剪下和貼上並不理想,但為了使RTCPeerConnection啟動並執行,除了整個替換之外別無選擇。

開啟index.html,單擊“ Start”按鈕以從網路攝像頭獲取視訊,然後單擊“ Call”以建立對等連線。您應該在兩個視訊元素中看到相同的視訊(來自您的網路攝像頭)。檢視瀏覽器控制檯以檢視WebRTC日誌記錄。

在WebRTC對等體之間建立呼叫涉及三個任務:
為呼叫的每一端建立一個RTCPeerConnection,並在每一端新增本地流getUserMedia()。
獲取和共享網路資訊:潛在的連線端點稱為ICE候選者。
獲取並共享本地和遠端描述:SDP格式的本地媒體元資料。

想象一下,Alice和Bob想要使用RTCPeerConnection來設定視訊聊天。
首先,Alice和Bob交換網路資訊。“查詢候選者”一詞是指使用ICE框架查詢網路介面和埠的過程。
Alice使用onicecandidate (addEventListener(‘icecandidate’))處理程式建立RTCPeerConnection物件。這對應於main.js中的以下程式碼:

let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);

Alice呼叫getUserMedia()並新增傳遞給它的流:

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中的處理程式onicecandidate被呼叫。
Alice將序列化的候選資料傳送給Bob。在實際應用程式中,此過程(信令)通過訊息傳遞服務進行。在此步驟中,兩個RTCPeerConnection物件位於同一頁面上,可以直接通訊,無需外部訊息傳遞。
當Bob從Alice獲取候選訊息時,他呼叫addIceCandidate(),將候選者新增到遠端對等描述中:

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對等體還需要找出並交換本地和遠端音訊和視訊媒體資訊,例如解析度和編解碼器功能。通過使用稱為SDP的會話描述協議格式交換元資料塊(稱為offer 和answer)來進行交換媒體配置資訊的信令:

1 Alice執行RTCPeerConnection createOffer()方法。返回的promise提供了一個RTCSessionDescription:Alice的本地會話描述:

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);

2 如果成功,Alice使用本地描述設定setLocalDescription(),然後通過其信令通道將此會話描述傳送給Bob。
3 Bob將Alice傳送給他的描述設定為使用的遠端描述setRemoteDescription()。
4 Bob執行RTCPeerConnection createAnswer()方法,向其傳遞從Alice獲得的遠端描述,因此可以生成與她相容的本地會話。createAnswer() promise 傳遞一個RTCSessionDescription:Bob設定為本地描述,並將其傳送給Alice。
5 當Alice獲得Bob的會話描述時,她將其設定為遠端描述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);
}

六、使用RTCDataChannel交換資料

你將學到什麼
如何在WebRTC端點(對等方)之間交換資料。

此步驟的完整版本位於步驟03資料夾中。

更新您的HTML
對於此步驟,您將使用WebRTC資料通道textarea在同一頁面上的兩個元素之間傳送文字。這不是很有用,但確實演示了WebRTC如何用於共享資料以及流式視訊。

從index.html中刪除視訊和按鈕元素,並使用以下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>

更新您的JavaScript

將main.js替換為step-03 / js / main.js的內容。

在對等體之間嘗試流資料:開啟index.html,按 Start 設定對等連線,在左側textarea輸入一些文字,然後單擊Send以使用WebRTC資料通道傳輸文字。

此程式碼使用RTCPeerConnection和RTCDataChannel來啟用文字訊息的交換。此步驟中的大部分程式碼與RTCPeerConnection示例相同。

sendData()和createConnection()功能有大部分新程式碼:

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

注意使用dataConstraint。可以配置資料通道以實現不同型別的資料共享 - 例如,優先考慮可靠的交付而不是效能。

七、建立信令服務以交換訊息

你將學到什麼

使用npm安裝專案依賴中規定的package.json
執行Node.js伺服器並使用node-static來提供靜態檔案。
使用Socket.IO在Node.js上設定訊息傳遞服務。
用它來建立“房間”並交換訊息。

此步驟的完整版本位於step-04資料夾中。

為了設定和維護WebRTC呼叫,WebRTC客戶端(對等方)需要交換元資料:候選人(網路)資訊。提供和回答提供有關媒體資訊的訊息,例如解析度和編解碼器。換句話說,在可以發生音訊,視訊或資料的對等流傳輸之前,需要交換元資料。此過程稱為信令。

在前面的步驟中,傳送方和接收方RTCPeerConnection物件位於同一頁面上,因此“信令”只是在物件之間傳遞元資料的問題。

在現實世界的應用程式中,傳送方和接收方RTCPeerConnections在不同裝置上的網頁中執行,您需要一種方式讓它們進行元資料通訊。

為此,您使用信令伺服器:可以在WebRTC客戶端(對等方)之間傳遞訊息的伺服器。實際的訊息是純文字:字串化的JavaScript物件。

先決條件:安裝Node.js.
為了執行此codelab的下一步(資料夾步驟04到步驟06),您需要使用Node.js在localhost上執行伺服器。

您可以下載並安裝由Node.js的這個連結

安裝後,您將能夠匯入後續步驟(執行npm install)所需的依賴項,以及執行一個小型localhost伺服器來執行codelab(執行node index.js)。這些命令將在以後需要時顯示。

關於該應用程式
WebRTC使用客戶端JavaScript API,但對於實際使用,還需要信令(訊息)伺服器,以及STUN和TURN伺服器。
在這一步中,您將構建一個簡單的Node.js信令伺服器,使用Socket.IO Node.js模組和JavaScript庫進行訊息傳遞。使用Node.js和Socket.IO會很有用,但並不重要; 訊息傳遞元件非常簡單。

Socket.IO的設計使得構建交換訊息的服務變得簡單,Socket.IO因其內建的“房間”概念而適合學習WebRTC信令。
在此示例中,伺服器(Node.js應用程式)在index.js中實現,在其上執行的客戶端(Web應用程式)在index.html中實現。

此步驟中的Node.js應用程式有兩個任務。

首先,它充當訊息中繼:

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

其次,它管理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);
}

我們簡單的WebRTC應用程式將允許最多兩個對等方共享一個房間。
HTML和JavaScript
更新index.html所以它看起來像這樣:


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

在此步驟中,您不會在頁面上看到任何內容:所有日誌記錄都在瀏覽器控制檯上完成。(要在Chrome中檢視控制檯,請按Ctrl-Shift-J。如果您使用的是Mac,Command-Option-J。)
用以下內容替換js / main.js:

'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以在Node.js上執行
在HTML檔案中,您可能已經看到您正在使用Socket.IO檔案:

<script src =“/ socket.io/socket.io.js”> </ script>

在工作目錄的頂層建立一個名為package.json的檔案,其中包含以下內容:

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

這是一個應用程式清單,它告訴Node Package Manager(npm)要安裝哪些專案依賴項。

要安裝依賴項(例如/socket.io/socket.io.js),請在工作目錄的命令列終端中執行以下命令:

npm install

您應該看到一個安裝日誌,結束如下所示:
在這裡插入圖片描述
在工作目錄的頂層(而不是在js目錄中)建立一個新檔案index.js並新增以下程式碼:

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

});

從命令列終端,在工作目錄中執行以下命令:

node index.js

在瀏覽器中,開啟localhost:8080。

每次開啟此URL時,系統都會提示您輸入房間名稱。要加入同一個房間,請每次選擇相同的房間名稱,例如“foo”。

開啟一個新標籤頁,然後再次開啟localhost:8080。選擇相同的房間名稱。

在第三個選項卡或視窗中開啟localhost:8080。再次選擇相同的房間名稱。

檢查每個選項卡中的控制檯:您應該從上面的JavaScript中看到日誌記錄。

八、結合對等連線和信令

你將學到什麼

使用在Node.js上執行的Socket.IO執行WebRTC信令服務
使用該服務在對等體之間交換WebRTC元資料。

此步驟的完整版本位於步驟05資料夾中。

替換HTML和JavaScript
用以下內容替換index.html的內容:

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

將js / main.js替換為步驟05 / js / main.js的內容。

執行Node.js伺服器

node index.js

在瀏覽器中,開啟localhost:8080。

在新選項卡或視窗中再次開啟localhost:8080。一個視訊元素將顯示本地流getUserMedia(),另一個將顯示通過RTCPeerconnection流式傳輸的“遠端”視訊。
注意:每次關閉客戶端選項卡或視窗時,都需要重新啟動Node.js伺服器。
可在瀏覽器控制檯中檢視日誌記錄

如果您遇到奇怪的快取問題,請嘗試以下方法:
按住ctrl並單擊“ 重新載入”按鈕進行硬重新整理
重啟瀏覽器
執行npm cache clean命令列。

九、拍照並通過資料通道分享

你將學到什麼

拍攝照片並使用canvas元素從中獲取資料。
與遠端使用者交換影象資料。

此步驟的完整版本位於步驟06資料夾中。

以前,您學習瞭如何使用RTCDataChannel交換文字訊息。此步驟可以共享整個檔案:在此示例中,通過getUserMedia()拍攝照片。

該步驟的核心部分如下:

建立資料通道。請注意,在此步驟中不要向對等連線新增任何媒體流。
使用以下方法捕獲使用者的網路攝像頭視訊流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);
  });
}

當用戶單擊“ Snap”按鈕時,從視訊流中獲取快照(視訊幀)並將其顯示在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);
}

當用戶單擊“ Send”按鈕時,將影象轉換為位元組並通過資料通道傳送:
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));
}
}
接收方將資料通道訊息位元組轉換回影象並將影象顯示給使用者:

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

獲取程式碼

用步驟06的內容替換work資料夾的內容。你的index.html檔案的工作,現在應該是這樣的:

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

該應用程式將建立一個隨機的房間ID,並將該ID新增到URL。在新的瀏覽器選項卡或視窗中開啟位址列中的URL。

單擊“ Snap & Send”按鈕,然後在頁面底部的另一個選項卡中檢視傳入區域。該應用在標籤之間傳輸照片。