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);
}
成功工作
将所有部分结合,现在你可以在不同设备的两个浏览器上相互交流而不再需要服务器。