原文标题:Learning WebRTC peer-to-peer communication, part 2
作者:Swizec Teller
WebRTC对等通信-在不同设备上连接浏览器
我们成功了!不需要服务器即可使不同设备上的两个浏览器相互交流,只需要在开始交互的时候使用服务器。
点击此处运行代码,在不同的设备上打开两个链接。
如何使用WebRTC连接不同设备上的浏览器
你可以使用WebRTC在无服务器时使浏览器相互交流,但是由于没有服务发现,我们需要一个发信服务器来使浏览器可以找到彼此。
步骤如下:
1.客户端1 对服务器说hi并注册。
2.客户端2 对服务器说hi并注册。
3.服务器记录身份信息(用户名)
4.客户端1命令服务器呼叫客户端2
5.服务器告诉客户端2有呼叫信息。
6.客户端2接收呼叫。
7.客户端1和2直接交流。
我们模仿MDN的WebRTC交流实例来形成代码框架。
WebSocket信令服务器
发信号是两个浏览器之间的交互过程,我们使用了WebSockets来完成。
服务器部分和MDN的例子相同,只需复制。
我们做出了一些改变使它可以对now.sh项目工作。名称上,我们移除了所有SSL部分。Zeit将我们的服务器包进一个安全的SSL 服务器,SSL服务器接着通过一个非加密连接与实际服务器进行交流。
如果没有SSL,WebSockets不能在现代浏览器中起作用。如果没有运行本地主机,它对自签名证书不工作。如果需要两个不在本地设备上的浏览器交流,必须确保一个SSL证书。
最简单的方法是在now
项目上进行。
连接信令服务器
通过40行helper类的WebSockets可以与服务器交流。实例化类,建立连接,收听信息。
我们在connetToSocket
中建立了一个new WebSocket
,加入一些反馈,期待好的效果。Onmessage
允许我们之后通过messageListeners
数组添加额外的message listeners 窗口。
SendToServer
允许向服务器发送JSON对象,并且addMsgListener
允许添加一个新的message listener 窗口。 我们使用它来连接PeerConnection
和服务器。
建立PeerConnection接口
从WebRTC part1 可以学到, 我们把RTCPeerConnection组件分成了help class。
以下148行代码完成了整个周期。我们之前讨论过此代码,这是重述。
Constructor
建立了一些实例变量,一个新的RTCPeerConnection
对象,告诉它用哪个iceServers
,连接本地event listeners,然后开始接受信令服务器信息,并添加媒体流到peerConnection
.
下一步是处理ICECandidate
,当有新连接时它将建立一个的交互连接。它会ping信令服务器,告诉信令服务器,这里有新的ICE candidate.
handleICECandidateEvent = event => {
if (event.candidate) {
this.signalingConnection.sendToServer({
type: "new-ice-candidate",
target: this.targetUsername,
candidate: event.candidate
});
}
};
这之后,我们得到了handleNegotiationNeededEvent
,当RTCPeerConnection
需要新连接时,它会被调用。(我也不知道是什么触发了它)
但是函数产生了一个新的连接请求,更新了本地SDP描述,并且告知信令服务器它在尝试连接。
handleNegotiationNeededEvent = () => {
const {
username,
targetUsername
} = this;
this.peerConnection
.createOffer()
.then(offer => this.peerConnection.setLocalDescription(offer))
.then(() =>
this.signalingConnection.sendToServer({
name: username,
target: targetUsername,
type: "video-offer",
sdp: this.peerConnection.localDescription
})
)
.catch(console.error);
};
处理信令消息
接着到了有趣的部分,处理信令服务器的消息。
onSignalingMessage = (msg) => {
switch (msg.type) {
case "video-answer": // Callee has answered our offer
this.videoAnswer(msg);
break;
case "new-ice-candidate": // A new ICE candidate has been received
this.newICECandidate(msg)
break;
case "hang-up": // The other peer has hung up the call
this.close()
break;
}
}
当接收新信息时,我们可以做许多事。把我们自己设置成接收方,向连接中添加新的candidate或关闭。
videoAnswer = ({
sdp
}) => {
this.peerConnection
.setRemoteDescription(new RTCSessionDescription(sdp))
.catch(console.error);
}
newICECandidate = ({
candidate
}) => {
this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
close = () => {
this.peerConnection.close();
this.peerConnection = null;
this.onClose()
}
这就是我们的PeerConnection对象。理论上,我们可以实例化许多PeerConnection对象来同时连接远端机器。
这将会是一个有趣的尝试。
将所有部件整合
把这些组合起来的,就是我们的WebRTCPeerConnectionWithServer
反应组件,经过用户界面,实例化helper类,处理用户点击按钮的过程。
点击此处查看GitHub完整文件。
以下是其中比较重要的部分:
call = user => {
this.setState({
targetUsername: user
});
this.createPeerConnection();
};
hangUp = () => {
this.signalingConnection.sendToServer({
name: this.state.username,
target: this.state.targetUsername,
type: "hang-up"
});
this.peerConnection.close();
};
createPeerConnection = () => {
if (this.peerConnection) return;
this.peerConnection = new PeerConnection({
gotRemoteStream: this.gotRemoteStream,
gotRemoteTrack: this.gotRemoteTrack,
signalingConnection: this.signalingConnection,
onClose: this.closeVideoCall,
localStream: this.state.localStream,
username: this.state.username,
targetUsername: this.state.targetUsername
});
};
closeVideoCall = () => {
this.remoteVideoRef.current.srcObject &&
this.remoteVideoRef.current.srcObject
.getTracks()
.forEach(track => track.stop());
this.remoteVideoRef.current.src = null;
this.setState({
targetUsername: null,
callDisabled: false
});
};
函数从call
开始启动,保存我们正在调用的状态,建立一个新的peer连接。
CreatePeerConnection
把所有信息传入PeerConnection类
。
HangUp
和closeVideoCall
共同工作来停止调用。我们需要两者因为其中一个是用户控制的,而当挂断命令从另一边传来的时候,另一个被调用。
最后一步
在粘合区有一条信令服务器发出的信息需要我们处理: 调用请求。
case "video-offer": // Invitation and offer to chat
this.createPeerConnection();
this.peerConnection.videoOffer(msg);
break;
当服务器告知需要连接时,我们需要在客户端创建一个新的PeerConnection
对象,并处理请求。处理请求意味着设置一个远程SDP描述并发送应答。
videoOffer = ({
sdp
}) => {
const {
username,
targetUsername
} = this;
this.peerConnection
.setRemoteDescription(new RTCSessionDescription(sdp))
.then(() => this.peerConnection.createAnswer())
.then(answer => {
return this.peerConnection.setLocalDescription(answer);
})
.then(() => {
this.signalingConnection.sendToServer({
name: username,
targetUsername: targetUsername,
type: "video-answer",
sdp: this.peerConnection.localDescription
});
})
.catch(console.error);
}
成功工作
将所有部分结合,现在你可以在不同设备的两个浏览器上相互交流而不再需要服务器。