本系列文章介绍使用WebRTC实现实时通信,原文参考 Real time communication with WebRTC

5. 使用PeerConnection传输视频流

你将学到

通过本节你将学到

  • 使用WebRTC shim抽象浏览器的实现差异,adapter.js
  • 使用RTCPeerConnection API传输视频流
  • 控制媒体采集和流

本节的所有内容位于 step-02 文件夹

What is RTCPeerConnection?

RTCPeerConnection是WebRTC用来传输视频、音频和交换数据的API。

本示例在同一个页面的两个RTCPeerConnection对象(也称为peer)之间建立一个连接。

可能没有多少实际用途,但是有利于理解RTCPeerConnection如何工作。

Add the adapter.js shim

main.js上面添加adapter.js的链接,index.html展示如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!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>

Install the RTCPeerConnection code

使用 step-02 文件夹的版本替换 main.js

Make the call

浏览器打开 index.html ,点击 Start 按钮将会从webcam获取视频,然后点击 Call 将建立peer连接。你将在另一个video标签看到相同的视频内容(都是从webcam采集)。打开浏览器console查看输入日志。

How it works

WebRTC使用RTCPeerConnection API在WebRTC客户端(也称为peer)之间建立连接来传输视频。

在本示例中,两个RTCPeerConnection对象位于同一个页面:pc1pc2。可能没有实际用途,但是有利于理解这些API如何工作。

在WebRTC peers之间建立连接需要调用3个任务:

  • 创建RTCPeerConnection,并添加从getUserMedia获取的流
  • 获取并分享网络信息:连接端点被称为 ICE candidate.
  • 获取并分享本地和远端的描述信息:SDP 格式的本地媒体的元数据

假设Alice和Bob想通过RTCPeerConnection来建立视频通话。

首先,Alice和Bob交互网络信息。finding candidates指的是使用ICE框架查找网络接口和端口的过程。

  1. Alice创建RTCPeerConnection对象并添加onicedandidate处理函数,main.js 相关代码如下:
1
2
3
4
5
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener('iceconnectionstatechange',
                                     handleConnectionChange);
  1. Alice调用getUserMedia()并添加相应的流:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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. 步骤1.中设置的oncandidate回调函数当网络candidate可用时将会被调用。

  2. Alice将candidate数据发送给Bob,实际应用程序中,这个过程(称为信令)是通过消息传递服务进行的–你在后面的学习章节将学到如何做的这一点。本示例中,两个PeerConnection对象位于同一页面,可以直接通信,所以不需要外部消息传递。

  3. 当Bob获取到Alice发送的candidate消息时,他调用addIceCandidate()将candidate添加到远端peer description。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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节点之间也需要交换本地和对端的音视频媒体信息,比如分辨率和编码信息。交换媒体配置信息的信令,通过交换metadata来进行,称为offer和answer,使用会话描述协议,即SDP:

1). Alice运行RTCPeerConnection createOffer方法。函数期望返回RTCSessionDescription: Alice的本地会话描述

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

2). 如果成功,Alice使用setLocalDescription()方法设置本地description,然后将该会话描述通过信令通道发送给Bob。 3). Bob将Alice发送给他的描述使用setRemoteDescription()方法设置为他的远端描述。 4). Bob运行RTCPeerConnection的createAnswer方法,传参将从Alice那里获取的远端描述,因此将会生成一个与Alice匹配的本地会话描述。createAnswer期望传递RTCSessionDescription:Bob将它设置为他的本地描述,并发送给Alice。 5). Alice接收到Bob的会话描述,通过setRemoteDescription()方法将它是指为她的远端描述

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 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);
}

6). Ping!

加分项(Bonus points)

  1. 浏览器观察chrome://webrtc-internals. 提供WebRTC统计和调试数据。(chrome://about列出所有的Chrome URL列表)
  2. CSS修改页面样式
  • 并排放置video.
  • 将按钮使用相同大小,设置大号文本.
  • 确保布局在移动端可用。
  1. 从Chrome Dev Tools console 查看localStream, localPeerConnection 和 remotePeerConnection。
  2. 从控制台,查看localPeerConnectionpc1.localDescription. 查看SDP格式是什么样子?

Tips

  • 本节学习的内容很多!查找其他资源来更详细的解释RTCPeerConnection,看看webrtc.org/start。该页面包含很多JavaScript框架的建议 — 如果你愿意使用WebRTC, 不用对这些APIs有异议.
  • 从adapter.js GitHub repo阅读更多相关adapter.js知识
  • 想了解世界上最好的视频聊天app是什么样子吗?了解下AppRTC, WebRTC项目为了WebRTC通话的示范app:app, code。通话建立时间小于500毫秒。

下一步

本节展示了怎样使用WebRTC在peer之间传输视频流,但是同样适用于data!

下一节将展示如何使用RTCDataChannel传输任意的数据流。