MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

WebRTC basics

这篇翻译不完整。请帮忙从英语翻译这篇文章

当你理解 WebRTC 架构之后, 你就可以阅读本篇文章了。本篇文章将带领你贯穿整个跨浏览器 RTC 应用的创建过程。到本章结束的时候,你就拥有了一个可以运行的点对点的数据通道和媒体通道。

本页的案例过期了! 不要再尝试他们了。

注意

由于近期 API 做了一些修改,一些比较老的案例需要修复,暂时不可用。

当前可以正常工作的的案例是:

正确的实现方式或许可以从标准中推断出来。

本页包含的其余的过期的信息,在 bugzilla

Shims

就像你想的那样,这样前卫的 API 肯定需要浏览器前缀才可以,然后再将它转换成通用的变量。

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;

RTCPeerConnection

这是使用 peer 创建连接的起始点。它接收一个配置选项,其中配置了用来创建连接的 ICE 服务器信息。

var pc = new RTCPeerConnection(configuration);

RTCConfiguration

 RTCConfiguration 对象包含了一些信息,这些信息是关于用来做 ICE 的 TURN 和 / 或 STUN 服务器的。这是用来确保大多数用户可以避免因 NAT 和防火墙导致的无法建立连接。

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"}
    ]
}

Google 运行了一个我们可以使用的公共 STUN 服务器。我也在 http://numb.viagenie.ca/ 创建了一个免费的可以访问 TURN 服务器的账户。可能你也想这么做,你只要替换到你自己的许可证书就可以了。

 

ICECandidate

 

创建好 PeerConnection 并传入可用的 STUNTURN 之后,当 ICE 框架找到可以使用 Peer 创建连接的 “候选者”,会立即触发一个事件(onicecandidate)。这些 “候选者” 就是 ICECandidate, 而且他们会在 PeerConnection#onicecandidate 事件中执行一个回调函数。

pc.onicecandidate = function (e) {
    // candidate exists in e.candidate
    if (!e.candidate) return;
    send("icecandidate", JSON.stringify(e.candidate));
};

回调函数执行之后,我们必须使用信令通道 (signal channel) 将候选发送到其他的点。在 Chrome 中,ICE 框架一般会找到重复的候选者,所以,我一般只发送第一个,然后将处理函数删除。Firfox 中将候选者包含在了 Offer SDP 中。

Signal Channel

现在我们有了 ICE 候选,然后需要将它发送到我们的另一端,以让它知道如何跟我们建立连接。然而,现在有一个先有鸡还是先有蛋的问题。我们想要 PeerConnection 发送数据到另一端,但是在那之前我们需要先把元数据发送过去……

现在就是用到信令通道(Signal Channel)的时候了。它的任何一个数据传输方法都允许两个端点之间交换信息。在本文中,我们将使用 FireBase。因为它设置起来灰常简单,并且不需要任何主机或者服务器端的代码编写。

现在我们想象只有两个两个方法:send(), 它将使用一个键,然后将数据赋值给它;recv() ,当一个键有值的时候会调用一个处理函数。

数据库的结构就像下面这样:

{
    "": {
        "candidate:": …
        "offer": …
        "answer": … 
    }
}

连接会被 roomId 分割,然后会保存四条信息:发起者 (oferer) 的 ICE 候选、应答者 (answerer) 的 ICE 候选、offer SDP 和 answer SDP.

Offer

Offer SDP 是用来向另一端描述期望格式(视频, 格式, 解编码, 加密, 解析度, 尺寸 等等)的元数据。

一次信息的交换需要从一端拿到 offer,然后另外一端接受这个 offer 然后返回一个 answer。

pc.createOffer(function (offer) {
    pc.setLocalDescription(offer, function() {
        send("offer", JSON.stringify(pc.localDescription);
    }, errorHandler);
}, errorHandler, options);

errorHandler

创建 offer 的过程如果出现问题,就会执行这个函数,并且将错误的详细信息作为第一个参数。

var errorHandler = function (err) {
    console.error(err);
};

options

Offer SDP 的选项.

var options = {
    offerToReceiveAudio: true,
    offerToReceiveVideo: true
};

offerToReceiveAudio/Video 告诉另一端,你想接收视频还是音频。对于 DataChannel 来说,这些是不需要的。

offer 创建好之后,我们必须将本地 SDP 设置进去,然后通过信令通道将它发送到另一端,之久就静静地等待另一端的 Answer SDP 吧.

Answer

Answer SDP 跟 offer 是差不多的,只不过它是作为相应的。有点像接电话一样。我们只能在接收到 offer 的时候创建一次 Answer.

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

DataChannel

我将首先阐述如何将 PeerConnection 用在 DataChannels API 上,并且如何在 peers 之间传输任意数据。

注意:撰写这篇文章的时候,DataChannels 在 Chrome 和 Firefox 之间是无法互用的。Chrome 支持了一个类似的私有协议,这个协议将在不久被标准协议支持。

var channel = pc.createDataChannel(channelName, channelOptions);

offer 的创建者和 channel 的创建者应该是同一个 peer。 answer 的创建者将在 PeerConnection 的 ondatachannel 回调中接收到 这个 channel。你必须在创建了这个 offer 之后立即调用 createDataChannel()。

channelName

这个字符串会作为 channel 名字的标签。注意:确保你 Channel 的名字中没有空格,否则在 Chrome 中调用 createAnswer() 将会失败。

channelOptions

var channelOptions = {};

当前 Chrome 还不支持这些选项,所以你可以将这些选项留空。检查 RFC 以获取更多关于这些选项的信息。

Channel 事件和方法

onopen

当连接建立的时候会被执行。

onerror

连接创建出错时执行。第一个参数是一个 error 对象。

channel.onerror = function (err) {
    console.error("Channel Error:", err);
};

onmessage

channel.onmessage = function (e) {
    console.log("Got message:", e.data);
}

这是连接的核心。当你接收到消息的时候,这个方法会执行。第一个参数是一个包含了数据、接收时间和其它信息的 event 对象。

onclose

当连接关闭的时候执行。

绑定事件

如果你是这个 channel 的创建者(offerer), 你可以直接在通过 createChannel 创建的 DataChannel 上绑定事件。如果你是 answerer 你必须使用 PeerConnection 的 ondatachannel 回调去访问这个 channel。

pc.ondatachannel = function (e) {
    e.channel.onmessage = function () { … };
};

这个 channel 可以在传递到回调函数中的 event 对象中以 e.channel 的方式访问。

send()

channel.send("Hi Peer!");

这个方法允许你直接传递数据到 peer!令人惊奇。你必须传递确保传递的数据是 String, Blob, ArrayBuffer 或者 ArrayBufferView 中的一种类型,所以,确保字符串化对象。

close()

在连接应该中止的时候关闭 channel。推荐在页面卸载的时候调用。

Media

现在我们将会涉及到如何传递诸如音视频的媒体的话题。要显示音视频,你必须在文档中加入包含 autoplay 属性的  <video> 标签。

Get User Media

<video id="preview" autoplay></video>
 
var video = document.getElementById("preview");
navigator.getUserMedia(constraints, function (stream) {
    video.src = URL.createObjectURL(stream);
}, errorHandler);

constraints

约定你想从用户获取到的媒体类型。

var constraints = {
    video: true,
    audio: true
};

如果你只想进行音频聊天,移除 video 成员。

errorHandler

当返回请求的媒体错误时被调用

Media Events and Methods

addStream

将从 getUserMedia 返回的流加入 PeerConnection。

pc.addStream(stream);

onaddstream

<video id="otherPeer" autoplay></video>
 
var otherPeer = document.getElementById("otherPeer");
pc.onaddstream = function (e) {
    otherPeer.src = URL.createObjectURL(e.stream);
};

当连接建立并且其它的 peer 通过 addStream 将流加入到了连接中时会被调用。你需要另外的 <video> 标签去显示其它 peer 的媒体。

第一个参数是包含了其它 peer 流媒体的 event 对象。

文档标签和贡献者

 此页面的贡献者: huangcheng, SparrowLiu
 最后编辑者: huangcheng,