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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
---
title: WebRTC basics
slug: Web/API/WebRTC_API/WebRTC_basics
translation_of: Web/API/WebRTC_API/Signaling_and_video_calling
translation_of_original: Web/API/WebRTC_API/WebRTC_basics
---
<p>{{WebRTCSidebar}}</p>
<div class="summary">
<p>当你理解 <a href="/en-US/docs/Web/API/WebRTC_API/Architecture" title="/en-US/docs/Web/Guide/API/WebRTC/WebRTC_architecture">WebRTC 架构</a>之后, 你就可以阅读本篇文章了。本篇文章将带领你贯穿整个跨浏览器 RTC 应用的创建过程。到本章结束的时候,你就拥有了一个可以运行的点对点的数据通道和媒体通道。</p>
</div>
<div class="note">
<p><strong>本页的案例过期了! 不要再尝试他们了。</strong></p>
</div>
<h2 id="注意">注意</h2>
<p>由于近期 API 做了一些修改,一些比较老的案例需要修复,暂时不可用。</p>
<ul>
<li><a href="http://louisstow.github.io/WebRTC/media.html">louisstow</a></li>
<li><a href="http://mozilla.github.io/webrtc-landing/">mozilla</a></li>
<li><a href="https://www.webrtc-experiment.com/">webrtc-experiment</a></li>
</ul>
<p>当前可以正常工作的的案例是:</p>
<ul>
<li><a href="https://apprtc.appspot.com">apprtc</a> (<a href="https://github.com/webrtc/apprtc">source</a>)</li>
</ul>
<p>正确的实现方式或许可以从<a href="http://w3c.github.io/webrtc-pc/">标准</a>中推断出来。</p>
<p>本页包含的其余的过期的信息,在 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1119285">bugzilla</a>。</p>
<h2 id="Shims">Shims</h2>
<p>就像你想的那样,这样前卫的 API 肯定需要浏览器前缀才可以,然后再将它转换成通用的变量。</p>
<pre class="brush: js">var RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;</pre>
<h2 id="RTCPeerConnection">RTCPeerConnection</h2>
<p>这是使用 peer 创建连接的起始点。它接收一个配置选项,其中配置了用来创建连接的 ICE 服务器信息。</p>
<pre class="brush: js">var pc = new RTCPeerConnection(configuration);</pre>
<h3 id="RTCConfiguration"><strong><code>RTCConfiguration</code></strong></h3>
<p> {{domxref("RTCConfiguration")}} 对象包含了一些信息,这些信息是关于用来做 ICE 的 TURN 和 / 或 STUN 服务器的。这是用来确保大多数用户可以避免因 NAT 和防火墙导致的无法建立连接。</p>
<pre class="brush: js">var configuration = {
iceServers: [
{urls: "stun:23.21.150.121"},
{urls: "stun:stun.l.google.com:19302"},
{urls: "turn:numb.viagenie.ca", credential: "webrtcdemo", username: "louis%40mozilla.com"}
]
}</pre>
<p>Google 运行了一个我们可以使用的<a href="https://code.google.com/p/natvpn/source/browse/trunk/stun_server_list">公共 STUN 服务器</a>。我也在 http://numb.viagenie.ca/ 创建了一个免费的可以访问 TURN 服务器的账户。可能你也想这么做,你只要替换到你自己的许可证书就可以了。</p>
<h2 id="ICECandidate">ICECandidate</h2>
<p>创建好 PeerConnection 并传入可用的 <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/API/WebRTC/WebRTC_architecture#What_is_STUN.3F">STUN</a> 和 <a href="https://developer.mozilla.org/en-US/docs/Web/Guide/API/WebRTC/WebRTC_architecture#What_is_TURN.3F">TURN</a> 之后,当 ICE 框架找到可以使用 Peer 创建连接的 “候选者”,会立即触发一个事件(onicecandidate)。这些 “候选者” 就是 ICECandidate, 而且他们会在 <a href="/en-US/docs/Web/API/RTCPeerConnection.onicecandidate">PeerConnection#onicecandidate</a> 事件中执行一个回调函数。</p>
<pre class="brush: js" lang="javascript">pc.onicecandidate = function (e) {
// candidate exists in e.candidate
if (!e.candidate) return;
send("icecandidate", JSON.stringify(e.candidate));
};</pre>
<p>回调函数执行之后,我们必须使用信令通道 (signal channel) 将候选发送到其他的点。在 Chrome 中,ICE 框架一般会找到重复的候选者,所以,我一般只发送第一个,然后将处理函数删除。Firfox 中将候选者包含在了 Offer SDP 中。</p>
<h2 id="Signal_Channel">Signal Channel</h2>
<p>现在我们有了 ICE 候选,然后需要将它发送到我们的另一端,以让它知道如何跟我们建立连接。然而,现在有一个先有鸡还是先有蛋的问题。我们想要 PeerConnection 发送数据到另一端,但是在那之前我们需要先把元数据发送过去……</p>
<p>现在就是用到信令通道(Signal Channel)的时候了。它的任何一个数据传输方法都允许两个端点之间交换信息。在本文中,我们将使用 <a href="http://firebase.com">FireBase</a>。因为它设置起来灰常简单,并且不需要任何主机或者服务器端的代码编写。</p>
<p>现在我们想象只有两个两个方法:send(), 它将使用一个键,然后将数据赋值给它;recv() ,当一个键有值的时候会调用一个处理函数。</p>
<p>数据库的结构就像下面这样:</p>
<pre class="brush: js" lang="json">{
"": {
"candidate:": …
"offer": …
"answer": …
}
}</pre>
<p>连接会被 roomId 分割,然后会保存四条信息:发起者 (oferer) 的 ICE 候选、应答者 (answerer) 的 ICE 候选、offer SDP 和 answer SDP.</p>
<h2 id="Offer">Offer</h2>
<p>Offer SDP 是用来向另一端描述期望格式(视频, 格式, 解编码, 加密, 解析度, 尺寸 等等)的元数据。</p>
<p>一次信息的交换需要从一端拿到 offer,然后另外一端接受这个 offer 然后返回一个 answer。</p>
<pre class="brush: js" lang="javascript">pc.createOffer(function (offer) {
pc.setLocalDescription(offer, function() {
send("offer", JSON.stringify(pc.localDescription));
}, errorHandler);
}, errorHandler, options);</pre>
<h3 id="errorHandler"><strong><code>errorHandler</code></strong></h3>
<p>创建 offer 的过程如果出现问题,就会执行这个函数,并且将错误的详细信息作为第一个参数。</p>
<pre class="brush: js" lang="javascript">var errorHandler = function (err) {
console.error(err);
};</pre>
<h3 id="options"><strong><code>options</code></strong></h3>
<p>Offer SDP 的选项.</p>
<pre class="brush: js" lang="javascript">var options = {
offerToReceiveAudio: true,
offerToReceiveVideo: true
};</pre>
<p><code>offerToReceiveAudio/Video</code> 告诉另一端,你想接收视频还是音频。对于 DataChannel 来说,这些是不需要的。</p>
<p>offer 创建好之后,我们必须将本地 SDP 设置进去,然后通过信令通道将它发送到另一端,之久就静静地等待另一端的 Answer SDP 吧.</p>
<h2 id="Answer">Answer</h2>
<p>Answer SDP 跟 offer 是差不多的,只不过它是作为相应的。有点像接电话一样。我们只能在接收到 offer 的时候创建一次 Answer.</p>
<pre class="brush: js" lang="javascript">recv("offer", function (offer) {
offer = new SessionDescription(JSON.parse(offer))
pc.setRemoteDescription(offer);
pc.createAnswer(function (answer) {
pc.setLocalDescription(answer, function() {
send("answer", JSON.stringify(pc.localDescription));
}, errorHandler);
}, errorHandler);
});</pre>
<h2 id="DataChannel">DataChannel</h2>
<p>我将首先阐述如何将 PeerConnection 用在 DataChannels API 上,并且如何在 peers 之间传输任意数据。</p>
<p><em>注意:撰写这篇文章的时候,DataChannels 在 Chrome 和 Firefox 之间是无法互用的。Chrome 支持了一个类似的私有协议,这个协议将在不久被标准协议支持。</em></p>
<pre class="brush: js" lang="javascript">var channel = pc.createDataChannel(channelName, channelOptions);</pre>
<p>offer 的创建者和 channel 的创建者应该是同一个 peer。 answer 的创建者将在 PeerConnection 的 ondatachannel 回调中接收到 这个 channel。你必须在创建了这个 offer 之后立即调用 createDataChannel()。</p>
<h3 id="channelName"><strong><code>channelName</code></strong></h3>
<p>这个字符串会作为 channel 名字的标签。注意:确保你 Channel 的名字中没有空格,否则在 Chrome 中调用 createAnswer() 将会失败。</p>
<h3 id="channelOptions"><strong><code>channelOptions</code></strong></h3>
<pre class="brush: js" lang="javascript">var channelOptions = {};</pre>
<p>当前 Chrome 还不支持这些选项,所以你可以将这些选项留空。检查 RFC 以获取更多关于这些选项的信息。</p>
<h3 id="Channel_事件和方法">Channel 事件和方法</h3>
<h4 id="onopen"><strong><code>onopen</code></strong></h4>
<p>当连接建立的时候会被执行。</p>
<h4 id="onerror"><strong><code>onerror</code></strong></h4>
<p>连接创建出错时执行。第一个参数是一个 error 对象。</p>
<pre class="brush: js" lang="javascript">channel.onerror = function (err) {
console.error("Channel Error:", err);
};</pre>
<h4 id="onmessage"><strong><code>onmessage</code></strong></h4>
<pre class="brush: js" lang="javascript">channel.onmessage = function (e) {
console.log("Got message:", e.data);
}</pre>
<p>这是连接的核心。当你接收到消息的时候,这个方法会执行。第一个参数是一个包含了数据、接收时间和其它信息的 event 对象。</p>
<h4 id="onclose"><strong><code>onclose</code></strong></h4>
<p>当连接关闭的时候执行。</p>
<h3 id="绑定事件"><strong>绑定事件</strong></h3>
<p>如果你是这个 channel 的创建者(offerer), 你可以直接在通过 createChannel 创建的 DataChannel 上绑定事件。如果你是 answerer 你必须使用 PeerConnection 的 ondatachannel 回调去访问这个 channel。</p>
<pre class="brush: js" lang="javascript">pc.ondatachannel = function (e) {
e.channel.onmessage = function () { … };
};</pre>
<p>这个 channel 可以在传递到回调函数中的 event 对象中以 e.channel 的方式访问。</p>
<h4 id="send()"><strong><code>send()</code></strong></h4>
<pre class="brush: js" lang="javascript">channel.send("Hi Peer!");</pre>
<p>这个方法允许你直接传递数据到 peer!令人惊奇。你必须传递确保传递的数据是 String, Blob, ArrayBuffer 或者 ArrayBufferView 中的一种类型,所以,确保字符串化对象。</p>
<h4 id="close()"><strong><code>close()</code></strong></h4>
<p>在连接应该中止的时候关闭 channel。推荐在页面卸载的时候调用。</p>
<h2 id="Media">Media</h2>
<p>现在我们将会涉及到如何传递诸如音视频的媒体的话题。要显示音视频,你必须在文档中加入包含 autoplay 属性的 <video> 标签。</p>
<h3 id="Get_User_Media">Get User Media</h3>
<pre class="brush: js"><video id="preview" autoplay></video>
var video = document.getElementById("preview");
navigator.getUserMedia(constraints, function (stream) {
video.src = URL.createObjectURL(stream);
}, errorHandler);</pre>
<p><strong><code>constraints</code></strong></p>
<p>约定你想从用户获取到的媒体类型。</p>
<pre class="brush: js" lang="javascript">var constraints = {
video: true,
audio: true
};</pre>
<p>如果你只想进行音频聊天,移除 video 成员。</p>
<h4 id="errorHandler_2"><strong><code>errorHandler</code></strong></h4>
<p>当返回请求的媒体错误时被调用</p>
<h3 id="Media_Events_and_Methods">Media Events and Methods</h3>
<h4 id="addStream"><strong><code>addStream</code></strong></h4>
<p>将从 getUserMedia 返回的流加入 PeerConnection。</p>
<pre class="brush: js" lang="javascript">pc.addStream(stream);</pre>
<h4 id="onaddstream"><strong><code>onaddstream</code></strong></h4>
<pre class="brush: js"><video id="otherPeer" autoplay></video>
var otherPeer = document.getElementById("otherPeer");
pc.onaddstream = function (e) {
otherPeer.src = URL.createObjectURL(e.stream);
};</pre>
<p>当连接建立并且其它的 peer 通过 addStream 将流加入到了连接中时会被调用。你需要另外的 <video> 标签去显示其它 peer 的媒体。</p>
<p>第一个参数是包含了其它 peer 流媒体的 event 对象。</p>
|