webrtc通信实践
nodejs
November 15, 2022
centos7 下安装 coturn
安装相关依赖
yum install -y make gcc cc gcc-c++ wget openssl-devel libevent libevent-devel openssl
yum install git
下载编译安装coturn开源项目
git clone https://github.com/coturn/coturn
cd coturn
./configure
make
make install
查看是否安装成功
将/usr/local/etc/目录下的turnserver.conf.default文件复制一份,文件名为turnserver.conf
cd /usr/local/etc/
cp turnserver.conf.default turnserver.conf
查看网卡,记录网卡名称和内网地址:ifconfig
cert和pkey配置的自签名证书用Openssl命令生成,提示的相关信息随便填写即可
openssl req -x509 -newkey rsa:2048 -keyout /usr/local/etc/turn_server_pkey.pem -out /usr/local/etc/turn_server_cert.pem -days 99999 -nodes
修改配置文件信息:vim /usr/local/etc/turnserver.conf
# 网卡名
relay-device=eth0
#内网IP
listening-ip=172.16.0.5
listening-port=3478
#内网IP
relay-ip=172.16.0.5
tls-listening-port=5349
# 外网IP
external-ip=134.xxx.xxx.xxx
relay-threads=500
#打开密码验证
lt-cred-mech
cert=/usr/local/etc/turn_server_cert.pem
pkey=/usr/local/etc/turn_server_pkey.pem
min-port=40000
max-port=65535
#设置用户名和密码,创建IceServer时使用
user=admin:123456
# 外网IP绑定的域名
realm=lisong.hn.cn
# 服务器名称,用于OAuth认证,默认和realm相同,部分浏览器本段不设可能会引发cors错误。
server-name=lisong.hn.cn
# 认证密码,和前面设置的密码保持一致
cli-password=123456
添加3478端口的tcp/udp规则
#开放端口
firewall-cmd --zone=public --add-port=3478/udp --permanent
firewall-cmd --zone=public --add-port=3478/tcp --permanent
#刷新防火墙
firewall-cmd --reload
#查看当前开放的端口
firewall-cmd --list-port
启动服务:turnserver -o -a -f。备注:turnserver命令会自动寻找到turnserver.conf文件的位置。
终止服务
#搜索 turnserveru 进程
ps -ef | grep turnserver
#杀掉进程(pid换成进程号)
kill -9 pid
客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webrtc</title>
<script src="./adapter-latest.js"></script>
</head>
<body>
<button onclick="doOffer()">加入会议</button>
<div id="videos">
<video id="localVideo" autoplay muted playsinline>本地窗口</video>
<video id="remoteVideo" autoplay playsinline>远端窗口</video>
</div>
<script>
var DO_OFFER = 1;
var DO_ANSWER = 2;
var SET_DESC = 3;
var ICE_CANDIDATE = 4;
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
var zeroRTCEngine = null;
var localStream = null;
var remoteStream = null;
var candidate = null;
var offerSession = null;
var answerSession = null;
class ZeroRTCEngine {
constructor(wsUrl) {
this.wsUrl = wsUrl;
this.signaling = null;
this.createWebsocket();
}
createWebsocket() {
this.signaling = new WebSocket(this.wsUrl);
this.signaling.onopen = function() {
console.log("websocket open");
}
this.signaling.onmessage = function(event) {
var message = event.data;
message = JSON.parse(message);
console.log('message:', message.type);
switch (message.type) {
case DO_ANSWER:
doAnswer(message.data);
break;
case ICE_CANDIDATE:
addIceCandidate(message.data);
break;
case SET_DESC:
setRemoteDescription(message.data);
}
}
this.signaling.onerror = function(event) {
console.log("onError: " + event.data);
}
this.signaling.onclose = function(event) {
console.log("onClose -> code: " + event.code + ", reason:" + EventTarget.reason);
}
}
sendMessage(message) {
this.signaling.send(message);
}
}
init();
function init() {
zeroRTCEngine = new ZeroRTCEngine('ws://127.0.0.1:8081/');
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then((stream) => {
localVideo.srcObject = stream;
localStream = stream;
createPeerConnection();
})
.catch(function(e) {
alert("getUserMedia() error: " + e.name);
});
}
function createPeerConnection() {
var defaultConfiguration = {
bundlePolicy: "max-bundle",
rtcpMuxPolicy: "require",
iceTransportPolicy: "all",
//iceTransportPolicy: "relay",
// 修改ice数组测试效果,需要进行封装
iceServers: [{
"urls": [
"turn:bwg.lisong.hn.cn:3478?transport=udp",
"turn:bwg.lisong.hn.cn:3478?transport=tcp" // 可以插入多个进行备选
],
"username": "test",
"credential": "123456"
},
{
"urls": [
"stun:bwg.lisong.hn.cn:3478"
]
}
]
};
pc = new RTCPeerConnection(defaultConfiguration); // 音视频通话的核心类
pc.onicecandidate = (event) => {
console.info("onicecandidate");
if (event.candidate) {
candidate = {
'sdpMLineIndex': event.candidate.sdpMLineIndex,
'sdpMid': event.candidate.sdpMid,
'candidate': event.candidate.candidate
};
zeroRTCEngine.sendMessage(JSON.stringify({
type: ICE_CANDIDATE,
data: candidate
}));
} else {
console.warn("End of candidates");
}
};
pc.ontrack = (event) => {
console.log('ontrack')
remoteStream = event.streams[0];
remoteVideo.srcObject = remoteStream;
};
pc.onconnectionstatechange = () => {
if (pc != null) {
console.info("ConnectionState -> " + pc.connectionState);
}
};
pc.oniceconnectionstatechange = () => {
if (pc != null) {
console.info("IceConnectionState -> " + pc.iceConnectionState);
}
}
localStream.getTracks().forEach((track) => pc.addTrack(track, localStream)); // 把本地流设置给RTCPeerConnection
}
function doOffer() {
if (pc == null) {
createPeerConnection();
}
pc.createOffer().then((session) => {
pc.setLocalDescription(session)
.then(function() {
offerSession = session;
zeroRTCEngine.sendMessage(JSON.stringify({
type: DO_OFFER,
data: session
}));
})
.catch(function(error) {
console.error("offer setLocalDescription failed: " + error);
});
});
}
function doAnswer(session) {
setRemoteDescription(session);
pc.createAnswer().then((session) => {
pc.setLocalDescription(session)
.then(function() {
answerSession = session;
zeroRTCEngine.sendMessage(JSON.stringify({
type: DO_ANSWER,
data: session
}));
})
.catch(function(error) {
console.error("answer setLocalDescription failed: " + error);
});
});
}
function addIceCandidate(candidate) {
console.log('addIceCandidate');
candidate = new RTCIceCandidate(candidate);
pc.addIceCandidate(candidate).catch(e => {
console.error("addIceCandidate failed:" + e.name);
});
}
function setRemoteDescription(session) {
console.log('setRemoteDescription');
pc.setRemoteDescription(session);
}
</script>
</body>
</html>
服务端
const WebSocket = require('ws');
var connMap = {};
var id = 0;
var DO_OFFER = 1;
var DO_ANSWER = 2;
var SET_DESC = 3;
var ICE_CANDIDATE = 4;
const webSocketServer = new WebSocket.Server({
port: 8081
});
webSocketServer.on('listening', () => {
console.log('web socket begins listening');
});
webSocketServer.on('connection', function connection(conn) {
var userId = id++;
console.log("New connection:", userId);
connMap[userId] = conn;
conn.userId = userId;
conn.on("message", function(str) {
try {
var message = JSON.parse(str);
console.log('recieve', userId, message.type);
switch (message.type) {
case DO_OFFER:
broadcast(userId, {
type: DO_ANSWER,
data: message.data
});
break;
case ICE_CANDIDATE:
broadcast(userId, {
type: ICE_CANDIDATE,
data: message.data
});
break;
case DO_ANSWER:
broadcast(userId, {
type: SET_DESC,
data: message.data
});
}
} catch (e) {
console.log(e);
}
});
conn.on("close", function(code, reason) {
console.log("Connection closed:", userId);
});
});
function broadcast(userId, msg) {
var type = msg.type;
msg = typeof msg === 'object' ? JSON.stringify(msg) : msg;
webSocketServer.clients.forEach(function(conn) {
if (conn.userId !== userId) {
conn.send(msg);
console.log('send', conn.userId, type);
}
});
}