0
点赞
收藏
分享

微信扫一扫

WebRTC基础实践 - 5.通过RTCPeerConnection传输流媒体视频


本节内容

在本节课程中, 我们将学习以下内容:


  • 使用WebRTC兼容库: ​​adapter.js​​, 来抹平各浏览器间的差异。
  • 通过 RTCPeerConnection API 传输流媒体视频。
  • 控制 media 的捕捉和传输。

本节的完整版代码位于 ​​step-02​​ 文件夹中。

RTCPeerConnection 简介

在WebRTC规范中, ​​RTCPeerConnection​​用于视频流/音频流、以及数据的传输。

下面的示例程序, 将会在一个页面上, 通过两个 RTCPeerConnection 对象建立一个连接通道。

这个demo本身没什么实用价值, 目的只是为了理解 RTCPeerConnection 的原理。

添加​​video​​元素及控制按钮

在 ​index.html​ 文件中, 配置两个 video 元素, 以及三个按钮:

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

第一个 video 元素(​​id="localVideo"​​​)用于展示通过 ​​getUserMedia()​​​ 获取到的本地视频流, 第二个 video 元素(​​id="remoteVideo"​​)则通过RTCPeerconnection, 接收并显示同样的视频。在实际应用中, 页面中一般都有两个 video 元素: 一个用来展示本地视频, 另一个用来播放远程传输过来的视频( 可以参考微信视频聊天界面, 其中有一大一小,两个视频展示窗口 )。

添加 adapter.js 兼容库

在 ​main.js​ 引用的前面, 引入 ​​adapter.js​​ 的最新版本:

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

adapter.js​ 是一个适配程序, 隔离了应用程序和规范之间的变更、前缀等差异.(当然, WebRTC实现所使用的标准和协议都已经是稳定版了, 有前缀的API也没几个。)

在本节课程中, 我们引入了 ​adapter.js​ 的最新版本。这个库对于实验和教程来说足够用了, 但如果想用于生产环境, 可能还需要进一步完善。 ​​adapter.js​​​ 的地址为: ​​https://github.com/webrtc/adapter​​, Github提供的服务让我们可以使用到最新的版本。

WebRTC 详细的交互日志, 请参考: ​​https://webrtc.org/web-apis/interop/​​。

现在, ​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

使用 ​​step-02/main.js​​​ 文件替换 ​​work/main.js​​ 文件。

按道理来说,在demo教程中,是不应该存在这种大量的复制粘贴行为的, 但没办法, RTCPeerConnection 相关的代码是一个整体, 要跑起来就必须全部配置好才行。

下面我们对代码进行详细的讲解。

拨打视频

浏览器中通过http协议打开 ​index.html​页面, 单击 ​Start​ 按钮获取摄像头的视频, 之后点击 ​Call​ 按钮来建立对等连接(peer connection)。 如果连接成功, 那么就可以在两个 video 中中看到同样的画面. 请打开浏览器的控制台, 查看 WebRTC 相关的日志信息。

原理简介

这一步做了很多的操作…


具体的内容比较复杂, 如果不关心实现过程, 可直接跳到下一节。跳过下面的步骤, 依然可以进行该教程的学习!


通过 RTCPeerConnection, 可以在 WebRTC 客户端之间创建连接, 来传输流媒体视频, 每个客户端就是一个端点(​peer​)。

本节的示例中, 两个 RTCPeerConnection 对象在同一个页面中: 即 ​​pc1​​​ 和 ​​pc2​​。 所以并没有什么实际价值, 只是用来演示api的使用。

在 WebRTC 客户端之间创建视频通话, 需要执行三个步骤:


  • 为每个客户端创建一个 RTCPeerConnection 实例, 并且通过 ​​getUserMedia()​​ 获取本地媒体流。
  • 获取网络信息并发送给对方: 有可能成功的连接点(endpoint), 被称为 ​​ICE​​ 候选。
  • 获取本地和远程描述信息并分享: ​​SDP​​ 格式的本地 media 元数据。

假设 Alice 和 Bob 想通过 RTCPeerConnection 进行视频聊天。

首先, Alice 和 Bob 需要交换双方的网络信息。 “寻找候选” 指的是通过 ​​ICE​​ 框架来查找可用网络和端口信息的过程。 可以分为以下步骤:

  1. Alice 创建一个 RTCPeerConnection 对象, 设置好 ​​onicecandidate​​ 回调 [即 ​​addEventListener('icecandidate', XXX)​​] 。 在 ​main.js​ 中对应的代码为:
    ​let localPeerConnection; ​​以及,
    ​localPeerConnection = new RTCPeerConnection(servers); localPeerConnection.addEventListener('icecandidate', handleConnection); localPeerConnection.addEventListener( 'iceconnectionstatechange', handleConnectionChange); ​


在本例中, RTCPeerConnection 构造函数的 ​​servers​​ 参数为 null。
​servers​​ 参数中, 可以指定 STUN 和 TURN 服务器相关的信息。



WebRTC 是为 peer-to-peer 网络设计的, 所以用户可以在大部分可以直连的网络中使用. 但现实情况非常复杂, WebRTC面临的真实环境是: 客户端程序需要穿透 ​​NAT网关​​ ,以及各类防火墙。 所以在直连失败的情况下, peer-to-peer 网络需要一种回退措施。



为了解决 peer-to-peer 直连通信失败的问题, WebRTC 通过 STUN 服务来获取客户端的公网IP, 并使用 TURN 作为中继服务器。 详细信息请参考: ​​WebRTC in the real world​​ 。



  1. Alice 调用 ​​getUserMedia()​​, 将获取到的本地 stream 传给 localVideo:
    ​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.'); ​
  2. 在网络候选者变为可用时, 步骤1中引入的 ​​onicecandidate​​ 回调函数, 会被执行。
  3. Alice 将序列化之后的候选者信息发送给 Bob。这个过程被称为 ​signaling​(信令), 实际应用中, 会通过消息服务来传递。 在后面的教程中会看到. 当然,在本节中, 因为两个 RTCPeerConnection 实例处于同一个页面, 所以可以直接通信, 不再需要外部消息服务。
  4. Bob从Alice处获得候选者信息后, 调用 ​​addIceCandidate()​​ 方法, 将候选信息传给 remote peer description:

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客户端还需要获取本地和远程的音频/视频媒体信息, 比如分辨率、编码/解码器的能力等等. 交换媒体配置信息的信令过程, 是通过交换元数据的blob数据进行的, 即一次 ​offer​ 与一次 ​answer​, 使用会话描述协议(Session Description Protocol), 简称 ​​SDP​​:


  1. Alice 执行 RTCPeerConnection 的 ​​createOffer()​​ 方法。返回的 promise 中提供了一个 RTCSessionDescription 对象: 其中包含 Alice 本地的会话描述信息:
    ​trace('localPeerConnection createOffer start.'); localPeerConnection.createOffer(offerOptions) .then(createdOffer).catch(setSessionDescriptionError); ​
  2. 如果执行成功, Alice 通过 ​​setLocalDescription()​​ 方法将本地会话信息保存, 接着通过信令通道, 将这些信息发送给Bob。
  3. Bob使用RTCPeerConnection的​​setRemoteDescription()​​方法, 将Alice传过来的远端会话信息填进去。
  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); } ​
  6. 然后, 视频会话就接通了!

练习与实践


  1. 在一个新标签页中打开 ​​chrome://webrtc-internals​​。 该页面提供了 WebRTC 相关的统计数据和调试信息。(Chrome 相关的功能url列举在 ​​chrome://about​​ 之中)
  2. 修改页面的CSS样式:


  • 将视频并排在一起。
  • 统一按钮的宽高, 使用更大的字号。
  • 适配移动端。


  1. 在Chrome控制台中(Chrome Dev Tools console), 查看 ​​localStream​​​, ​​localPeerConnection​​​ 和 ​​remotePeerConnection​​ 对象。
  2. 在控制台中, 查看 ​​localPeerConnectionpc1.localDescription​​。看看 SDP 格式具体是什么样的?

知识点回顾

在本节课程中, 我们学习了:


  • 使用WebRTC兼容库: ​​adapter.js​​, 来抹平各浏览器间的差异。
  • 通过 RTCPeerConnection API 传输流媒体视频。
  • 控制 media 的捕捉和传输。
  • 在两个端点(peer)间共享 media 和网络信息, 以接通WebRTC视频通话。

本节的完整版代码位于 ​​step-02​​ 文件夹中。

提示


  • 本节涉及的知识点很多! 关于 RTCPeerConnection 的更多信息, 请参考 ​​webrtc.org/start​​. 里面有一些对 JavaScript 框架的建议, 如果想使用WebRTC, 也想深入了解API细节的话。
  • 参考 ​​adapter.js GitHub repo​​ 仓库, 获取更多信息。
  • 如果想要体验当下最先进的WebRTC视频聊天应用, 可以看看 AppRTC, 这也是WebRTC项目的标准实现: app访问地址: ​​https://appr.tc/​​​, 代码地址 ​​https://github.com/webrtc/apprtc​​。 创建通话的时间可以控制在 500 ms以内。

最佳实践

  • 想要让代码跟上时代的部分, 请使用基于Promise的API, 并通过 ​​adapter.js​​ 来兼容各种浏览器。

后续内容

本节演示了在两个WebRTC端点之间传输视频流 —— 后续小节将会展示如何传输数据!

接下来, 我们将学习 RTCDataChannel, 并用它来传输任意的数据内容。

翻译日期: 2018年07月12日


举报

相关推荐

0 条评论