为了方便测试webrtc通信功能,我写了一个简单的测试demo分享一下
1.服务端使用nodejs搭建,版本不限。使用的第三方module为express、nodejs-websocket,可以使用npm命令来安装
服务端实现了简单的信令协商,代码如下:
// server.js
var ws = require("nodejs-websocket");
var fs = require('fs');
var https = require('https')
var express = require('express');
var app = express();
var userList = {};
var options = {
secure: true,
key: fs.readFileSync('ssl-cert-test.key'),
cert: fs.readFileSync('ssl-cert-test.pem')
};
app.use(express.static(__dirname));
app.get('/', (req, res) => res.send('Hello World!'));
https.createServer(options, app).listen(1443);
var server = ws.createServer(options, function (conn) {
console.log("New connection");
conn.on("text", function (str) {
var obj = JSON.parse(str);
if (obj.action == "login" && obj.name != undefined) {
if (obj.name in userList) {
userList[obj.name].username = null;
delete userList[obj.name];
}
userList[obj.name] = conn;
conn.username = obj.name;
console.log("User login: " + obj.name);
}
else if (obj.action == "forward" && obj.to != undefined) {
if (obj.to in userList) {
var tConn = userList[obj.to];
tConn.sendText(obj.msg);
console.log(conn.username + " forward to: " + obj.to);
}
}
else if (obj.action == "logout" && obj.name != undefined) {
delete userList[conn.username];
}
})
conn.on("close", function (code, reason) {
console.log("Connection closed: " + code);
if (conn.username != null || conn.username != undefined) {
delete userList[conn.username];
console.log("User closed: " + conn.username);
}
});
conn.on("error", function (err) {
console.log("Connection err: " + err);
});
}).listen(8001);
需要注意的是服务端提供的是 https 以及 wss 服务(走TLS),因此要用到证书和私钥文件。我们自己可以使用openssl来自签名一个证书,具体方法可以自行搜索相关文章。
2.web前端测试代码:
<!doctype html>
<html>
<head>
<title>WebSocket Chat Demo with WebRTC Calling</title>
<meta charset="utf-8">
<script>
var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
var GET_USER_MEDIA = navigator.getUserMedia ? "getUserMedia" :
navigator.mozGetUserMedia ? "mozGetUserMedia" :
navigator.webkitGetUserMedia ? "webkitGetUserMedia" : "getUserMedia";
var v = document.createElement("video");
var SRC_OBJECT = 'srcObject' in v ? "srcObject" :
'mozSrcObject' in v ? "mozSrcObject" :
'webkitSrcObject' in v ? "webkitSrcObject" : "srcObject";
// url后面加 ‘#’ 的,认为是发起的offer提供者
var isCaller = window.location.href.indexOf('#') != -1;
///
// webrtc必须使用https,而websocket也需要使用https,并且会验证证书有效性,因此自己颁发了一套证书,并且绑定域名 *.test.com,测试时hosts文件还需要修改一下,将 wss.test.com 指定到server.js 运行的ip地址
var socket = new WebSocket("wss://wss.test.com:8001");
socket.onopen = function() {
if (isCaller) {
socket.send(JSON.stringify({
"action": "login",
"name": "caller"
}));
}
else {
socket.send(JSON.stringify({
"action": "login",
"name": "answer"
}));
}
};
/*
var socket2 = new WebSocket("wss://wss.test.com:8001");
socket2.onopen = function() {
socket2.send(JSON.stringify({
"action": "login",
"name": "caller2"
}));
};
socket2.onmessage = function(event) {
var msg = JSON.parse(event.data);
console.log(msg);
}
*/
function forward() {
socket.send(JSON.stringify({
"action": "forward",
"to": "caller2",
"msg": JSON.stringify({
"event": "offer",
"sdp": "xxxxxx"
})
}));
}
///
var iceServers = {
iceServers: [ // Information about ICE servers - Use your own!
{
urls: "turn:webrtc-from-chat.glitch.me", // A TURN server
username: "webrtc",
credential: "turnserver"
}
]
};
// 不使用ice协商服务器
var peerConnection = new PeerConnection();
// 发送ICE候选到其他客户端
peerConnection.onicecandidate = function(event){
if (event.candidate !== null) {
console.log("ICE: " + JSON.stringify(event.candidate));
if (!isCaller) {
var to = isCaller?"answer":"caller";
socket.send(JSON.stringify({
"action": "forward",
"to": to,
"msg": JSON.stringify({
"event": "_ice_candidate",
"candidate": event.candidate
})
}));
}
}
};
peerConnection.ontrack = function(event) {
document.getElementById('remoteVideo').srcObject = event.streams[0];
var c = event.track.getConstraints();
console.log(c);
};
var sendOffer = function (desc) {
// 设置本地Offer
peerConnection.setLocalDescription(desc);
// FIXME 发送sdp给对方
socket.send(JSON.stringify({
"action": "forward",
"to": "answer",
"msg": JSON.stringify({
"event": "_offer",
"sdp": desc
})
}));
}
var sendAnswer = function (desc) {
// 设置本地Offer
peerConnection.setLocalDescription(desc);
// FIXME 发送sdp给对方
socket.send(JSON.stringify({
"action": "forward",
"to": "caller",
"msg": JSON.stringify({
"event": "_answer",
"sdp": desc
})
}));
}
socket.onmessage = function(event) {
var msg = JSON.parse(event.data);
console.log(msg);
//如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
if( msg.event === "_ice_candidate" ){
peerConnection.addIceCandidate(new RTCIceCandidate(msg.candidate));
} else {
peerConnection.setRemoteDescription(new RTCSessionDescription(msg.sdp));
// 如果是一个offer,那么需要回复一个answer
if(msg.event === "_offer") {
peerConnection.createAnswer(sendAnswer, function (error) {
console.log('Failure callback: ' + error);
});
}
}
}
function openRtc() {
navigator.getUserMedia({
audio: true, // 是否开启麦克风
video: true // 是否开启摄像头,这里还可以进行更多的配置
}, function(stream){
// 测试枚举音频轨道
var tracks = stream.getAudioTracks();
// 绑定媒体流到video标签用于输出
document.getElementById('localVideo').srcObject = stream;
// 向PeerConnection中加入需要发送的流
peerConnection.addStream(stream);
/*var transceivers = peerConnection.getTransceivers();
transceivers.forEach(transceiver => {
// 生成只接收媒体数据的 sdp
transceiver.direction = "recvonly";
});*/
// 生成offer的选项
//var offOption = {offerToReceiveVideo: false, offerToReceiveAudio: false};
if (isCaller) {
// 如果是发起方则发送一个offer信令
peerConnection.createOffer(sendOffer, function (error) {
console.log('Failure callback: ' + error);
});
}
}, function(error){
// 获取本地视频流失败
console.log("获取本地视频流失败");
});
}
</script>
</head>
<body>
<div id="videoContainer">
<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay></video>
</div>
<button onclick="openRtc()">click</button>
</body>
</html>