一对一通话
(1)信令设计;(2)媒体协商;(3)加入Stream/Track;(4)网络协商 四大块继续讲解通话原理
信令协议设计
- join 加入房间
1 var jsonMsg = {
2 'cmd': 'join',
3 'roomId': roomId,
4 'uid': localUserId,
5 };
- respjoin 当join房间后发现房间已经存在另一个人时则返回另一个人的uid;如果只有自己则不返回
1 jsonMsg = {
2 'cmd': 'resp‐join',
3 'remoteUid': remoteUid
4 };
- leave 离开房间,服务器收到leave信令则检查同一房间是否有其他人,如果有其他人则通知他有人离开
1 var jsonMsg = {
2 'cmd': 'leave',
3 'roomId': roomId,
4 'uid': localUserId,
5 };
- newpeer 服务器通知客户端有新人加入,收到newpeer则发起连接请求
1 var jsonMsg = {
2 'cmd': 'new‐peer',
3 'remoteUid': uid
4 };
- peerleave 服务器通知客户端有人离开
1 var jsonMsg = {
2 'cmd': 'peer‐leave',
3 'remoteUid': uid
4 };
- offer 转发offer sdp
1 var jsonMsg = {
2 'cmd': 'offer',
3 'roomId': roomId,
4 'uid': localUserId,
5 'remoteUid':remoteUserId,
6 'msg': JSON.stringify(sessionDescription)
7 };
- answer 转发answer sdp
1 var jsonMsg = {
2 'cmd': 'answer',
3 'roomId': roomId,
4 'uid': localUserId,
5 'remoteUid':remoteUserId,
6 'msg': JSON.stringify(sessionDescription)
7 };
- candidate 转发candidate sdp
1 var jsonMsg = {
2 'cmd': 'candidate',
3 'roomId': roomId,
4 'uid': localUserId,
5 'remoteUid':remoteUserId,
6 'msg': JSON.stringify(candidateJson)
7 };
媒体协商
媒体协商
createOffer
基本格式
aPromise = myPeerConnection.createOffer([options]);
createAnswer
基本格式
aPromise = RTCPeerConnection .createAnswer([ options ]);
1 var options = {
2 offerToReceiveAudio: true, // 告诉另一端,你是否想接收音频,默认true
3 offerToReceiveVideo: true, // 告诉另一端,你是否想接收视频,默认true
4 iceRestart: false, // 是否在活跃状态重启ICE网络协商
5 };
setLocalDescription
基本格式
aPromise = RTCPeerConnection .setLocalDescription(sessionDescription);
setRemoteDescription
基本格式
aPromise = pc.setRemoteDescription(sessionDescription);
加入Stream/Track
addTrack
基本格式
rtpSender = rtcPeerConnection .addTrack(track,stream …);
track:添加到RTCPeerConnection中的媒体轨(音频track/视频track)
stream:getUserMedia中拿到的流,指定track所在的stream
网络协商
addIceCandidate
基本格式
aPromise = pc.addIceCandidate(候选人);
实现WebRTC音视频通话
开发步骤
- 客户端显示界面
//index.html
<html>
<head>
<title>WebRTC demo</title>
</head>
<h1>WebRTC demo</h1>
<div id="buttons">
<input id="zero-RoomId" type="text" placeholder="请输入房间ID" maxlength="40"/>
<button id="joinBtn" type="button">加入</button>
<button id="leaveBtn" type="button">离开</button>
</div>
<div id="videos">
<video id="localVideo" autoplay muted playsinline>本地窗口</video>
<video id="remoteVideo" autoplay playsinline>远端窗口</video>
</div>
<script src="js/main.js"></script>
</html>
//main.js
'use strict';
var localVideo = document.querySelector('#localVideo');//拿到页面控件
var remoteVideo = document.querySelector('#remoteVideo');
var localStream = null;
function openLocalStream(stream) {
console.log('Open local stream');
localVideo.srcObject = stream;
localStream = stream;
}
function initLocalStream() {
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(openLocalStream)
.catch(function(e) {
alert("getUserMedia() error: " + e.name);
});
}
document.getElementById('joinBtn').onclick = function() {
console.log("加入按钮被点击");3
// 初始化本地码流
initLocalStream();
}
- 打开摄像头并显示到页面
- websocket连接
//**在main.js添加**
var zeroRTCEngine;
var ZeroRTCEngine = function(wsUrl) {
this.init(wsUrl);
zeroRTCEngine = this;
return this;
}
ZeroRTCEngine.prototype.init = function(wsUrl) {
// 设置websocket url
this.wsUrl = wsUrl;
/** websocket对象 */
this.signaling = null;
}
ZeroRTCEngine.prototype.createWebsocket = function() {
zeroRTCEngine = this;
zeroRTCEngine.signaling = new WebSocket(this.wsUrl);
zeroRTCEngine.signaling.onopen = function() {
zeroRTCEngine.onOpen();
}
zeroRTCEngine.signaling.onmessage = function(ev) {
zeroRTCEngine.onMessage(ev);
}
zeroRTCEngine.signaling.onerror = function(ev) {
zeroRTCEngine.onError(ev);
}
zeroRTCEngine.signaling.onclose = function(ev) {
zeroRTCEngine.onClose(ev);
}
}
ZeroRTCEngine.prototype.onOpen = function() {
console.log("websocket open");
}
ZeroRTCEngine.prototype.onMessage = function(event) {
console.log("onMessage: " + event.data);
}
ZeroRTCEngine.prototype.onError = function(event) {
console.log("onError: " + event.data);
}
ZeroRTCEngine.prototype.onClose = function(event) {
console.log("onClose -> code: " + event.code + ", reason:" + EventTarget.reason);
}
zeroRTCEngine = new ZeroRTCEngine("ws://192.168.150.129:8099");
zeroRTCEngine.createWebsocket();
//signal_server.js
var ws = require("nodejs-websocket")
var prort = 8099;
var server = ws.createServer(function(conn){
console.log("创建一个新的连接--------")
conn.sendText("我收到你的连接了....");
conn.on("text", function(str) {
console.info("recv msg:" + str);
});
conn.on("close", function(code, reason) {
console.info("连接关闭 code: " + code + ", reason: " + reason);
});
conn.on("error", function(err) {
console.info("监听到错误:" + err);
});
}).listen(prort);
思路:
(1)收到newpeer (handleRemoteNewPeer处理),作为发起者创建RTCPeerConnection,绑定事件响应函数,
加入本地流;
(2)创建offer sdp,设置本地sdp,并将offer sdp发送到服务器;
(3)服务器收到offer sdp 转发给指定的remoteClient;
(4)接收者收到offer,也创建RTCPeerConnection,绑定事件响应函数,加入本地流;
(5)接收者设置远程sdp,并创建answer sdp,然后设置本地sdp并将answer sdp发送到服务器;
(6)服务器收到answer sdp 转发给指定的remoteClient;
(7)发起者收到answer sdp,则设置远程sdp;
(8)发起者和接收者都收到ontrack回调事件,获取到对方码流的对象句柄;
(9)发起者和接收者都开始请求打洞,通过onIceCandidate获取到打洞信息(candidate)并发送给对方
(10)如果P2P能成功则进行P2P通话,如果P2P不成功则进行中继转发通话。