逻辑图
服务端代码
var ws = require("nodejs-websocket")
var prort = xxxx;
const SIGNAL_TYPE_JOIN = "join";
const SIGNAL_TYPE_RESP_JOIN = "resp-join";
const SIGNAL_TYPE_LEAVE = "leave";
const SIGNAL_TYPE_NEW_PEER = "new-peer";
const SIGNAL_TYPE_PEER_LEAVE = "peer-leave";
const SIGNAL_TYPE_OFFER = "offer";
const SIGNAL_TYPE_ANSWER = "answer";
const SIGNAL_TYPE_CANDIDATE = "candidate";
var ZeroRTCMap = function () {
this._entrys = new Array();
this.put = function (key, value) {
if (key == null || key == undefined) {
return;
}
var index = this._getIndex(key);
if (index == -1) {
var entry = new Object();
entry.key = key;
entry.value = value;
this._entrys[this._entrys.length] = entry;
} else {
this._entrys[index].value = value;
}
};
this.get = function (key) {
var index = this._getIndex(key);
return (index != -1) ? this._entrys[index].value : null;
};
this.remove = function (key) {
var index = this._getIndex(key);
if (index != -1) {
this._entrys.splice(index, 1);
}
};
this.clear = function () {
this._entrys.length = 0;
};
this.contains = function (key) {
var index = this._getIndex(key);
return (index != -1) ? true : false;
};
this.size = function () {
return this._entrys.length;
};
this.getEntrys = function () {
return this._entrys;
};
this._getIndex = function (key) {
if (key == null || key == undefined) {
return -1;
}
var _length = this._entrys.length;
for (var i = 0; i < _length; i++) {
var entry = this._entrys[i];
if (entry == null || entry == undefined) {
continue;
}
if (entry.key === key) {
return i;
}
}
return -1;
};
}
var roomTableMap = new ZeroRTCMap();
function Client(uid, conn, roomId) {
this.uid = uid;
this.conn = conn;
this.roomId = roomId;
}
function handleJoin(message, conn) {
var roomId = message.roomId;
var uid = message.uid;
console.info("uid: " + uid + "try to join room " + roomId);
var roomMap = roomTableMap.get(roomId);
if (roomMap == null) {
roomMap = new ZeroRTCMap();
roomTableMap.put(roomId, roomMap);
}
if(roomMap.size() >= 2) {
console.error("roomId:" + roomId + " 已经有两人存在,请使用其他房间");
return;
}
var client = new Client(uid, conn, roomId);
roomMap.put(uid, client);
if(roomMap.size() > 1) {
var clients = roomMap.getEntrys();
for(var i in clients) {
var remoteUid = clients[i].key;
if (remoteUid != uid) {
var jsonMsg = {
'cmd': SIGNAL_TYPE_NEW_PEER,
'remoteUid': uid
};
var msg = JSON.stringify(jsonMsg);
var remoteClient =roomMap.get(remoteUid);
console.info("new-peer: " + msg);
remoteClient.conn.sendText(msg);
jsonMsg = {
'cmd':SIGNAL_TYPE_RESP_JOIN,
'remoteUid': remoteUid
};
msg = JSON.stringify(jsonMsg);
console.info("resp-join: " + msg);
conn.sendText(msg);
}
}
}
}
function handleLeave(message) {
var roomId = message.roomId;
var uid = message.uid;
console.info("uid: " + uid + "leave room " + roomId);
var roomMap = roomTableMap.get(roomId);
if (roomMap == null) {
console.error("handleLeave can't find then roomId " + roomId);
return;
}
roomMap.remove(uid);
if(roomMap.size() >= 1) {
var clients = roomMap.getEntrys();
for(var i in clients) {
var jsonMsg = {
'cmd': 'peer-leave',
'remoteUid': uid
};
var msg = JSON.stringify(jsonMsg);
var remoteUid = clients[i].key;
var remoteClient = roomMap.get(remoteUid);
if(remoteClient) {
console.info("notify peer:" + remoteClient.uid + ", uid:" + uid + " leave");
remoteClient.conn.sendText(msg);
}
}
}
}
var server = ws.createServer(function(conn){
console.log("创建一个新的连接--------")
conn.sendText("我收到你的连接了....");
conn.on("text", function(str) {
console.info("recv msg:" + str);
var jsonMsg = JSON.parse(str);
switch (jsonMsg.cmd) {
case SIGNAL_TYPE_JOIN:
handleJoin(jsonMsg, conn);
break;
case SIGNAL_TYPE_LEAVE:
handleLeave(jsonMsg);
break;
}
});
conn.on("close", function(code, reason) {
console.info("连接关闭 code: " + code + ", reason: " + reason);
});
conn.on("error", function(err) {
console.info("监听到错误:" + err);
});
}).listen(prort);
客户端代码
这边代码主要是对于 基础业务进行的类的封装 封装如ZeroRTCEngine 对象中
将websocket url 和 websocket对象的基础操作做了进一步封装操作
比如 如果 首先 将websocket对象 变为ZeroRTCEngine中的一个基础变量 对应的websocket 的各项操作 加了一层封装 主要逻辑由ZeroRTCEngine的对应函数负责。
zeroRTCEngine.signaling = new WebSocket(this.wsUrl);
zeroRTCEngine.signaling.onmessage = function(ev) {
zeroRTCEngine.onMessage(ev);
}
ZeroRTCEngine.prototype.onMessage = function(event) {
console.log("onMessage: " + event.data);
var jsonMsg = JSON.parse(event.data);
switch(jsonMsg.cmd) {
case SIGNAL_TYPE_NEW_PEER:
handleRemoteNewPeer(jsonMsg);
break;
case SIGNAL_TYPE_RESP_JOIN:
handleResponseJoin(jsonMsg);
break;
case SIGNAL_TYPE_PEER_LEAVE:
handleRemotePeerLeave(jsonMsg);
break;
}
绑定本地流并且显示代码块
document.getElementById('joinBtn').onclick = function() {
roomId = document.getElementById('zero-roomId').value;
if( roomId == "" || roomId == "请输入房间ID") {
alert("请输入房间ID");
return;
}
console.log("加入按钮被点击, roomId: " + roomId);
initLocalStream();
}
function openLocalStream(stream) {
console.log('Open local stream');
doJoin(roomId);
localVideo.srcObject = stream;
localStream = stream;
}
function initLocalStream() {
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(openLocalStream)
.catch(function(e) {
alert("getUserMedia() error: " + e.name);
});
}
客户端逻辑代码
'use strict';
const SIGNAL_TYPE_JOIN = "join";
const SIGNAL_TYPE_RESP_JOIN = "resp-join";
const SIGNAL_TYPE_LEAVE = "leave";
const SIGNAL_TYPE_NEW_PEER = "new-peer";
const SIGNAL_TYPE_PEER_LEAVE = "peer-leave";
const SIGNAL_TYPE_OFFER = "offer";
const SIGNAL_TYPE_ANSWER = "answer";
const SIGNAL_TYPE_CANDIDATE = "candidate";
var localUserId = Math.random().toString(36).substr(2);
var remoteUserId = -1;
var roomId = 0;
var localVideo = document.querySelector('#localVideo');
var remoteVideo = document.querySelector('#remoteVideo');
var localStream = null;
var zeroRTCEngine;
var ZeroRTCEngine = function(wsUrl) {
this.init(wsUrl);
zeroRTCEngine = this;
return this;
}
ZeroRTCEngine.prototype.init = function(wsUrl) {
this.wsUrl = wsUrl;
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);
var jsonMsg = JSON.parse(event.data);
switch(jsonMsg.cmd) {
case SIGNAL_TYPE_NEW_PEER:
handleRemoteNewPeer(jsonMsg);
break;
case SIGNAL_TYPE_RESP_JOIN:
handleResponseJoin(jsonMsg);
break;
case SIGNAL_TYPE_PEER_LEAVE:
handleRemotePeerLeave(jsonMsg);
break;
}
}
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.prototype.sendMessage = function(message) {
this.signaling.send(message);
}
function handleResponseJoin(message) {
console.info("handleResponseJoin, remoteUid: " + message.remoteUid);
remoteUserId = message.remoteUid;
}
function handleRemotePeerLeave(message) {
console.info("handleRemotePeerLeave, remoteUid: " + message.remoteUid);
remoteVideo.srcObject = null;
}
function handleRemoteNewPeer(message) {
console.info("handleRemoteNewPeer, remoteUid: " + message.remoteUid);
remoteUserId = message.remoteUid;
}
function doJoin(roomId) {
var jsonMsg = {
'cmd': 'join',
'roomId': roomId,
'uid': localUserId,
};
var message = JSON.stringify(jsonMsg);
zeroRTCEngine.sendMessage(message);
console.info("doJoin message: " + message);
}
function doLeave() {
var jsonMsg = {
'cmd': 'leave',
'roomId': roomId,
'uid': localUserId,
};
var message = JSON.stringify(jsonMsg);
zeroRTCEngine.sendMessage(message);
console.info("doLeave message: " + message);
}
function openLocalStream(stream) {
console.log('Open local stream');
doJoin(roomId);
localVideo.srcObject = stream;
localStream = stream;
}
function initLocalStream() {
navigator.mediaDevices.getUserMedia({
audio: true,
video: true
})
.then(openLocalStream)
.catch(function(e) {
alert("getUserMedia() error: " + e.name);
});
}
zeroRTCEngine = new ZeroRTCEngine("ws://192.168.221.134:8099");
zeroRTCEngine.createWebsocket();
document.getElementById('joinBtn').onclick = function() {
roomId = document.getElementById('zero-roomId').value;
if( roomId == "" || roomId == "请输入房间ID") {
alert("请输入房间ID");
return;
}
console.log("加入按钮被点击, roomId: " + roomId);
initLocalStream();
}
document.getElementById('leaveBtn').onclick = function() {
console.log("离开按钮被点击");
doLeave();
}
界面代码
####
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> WebRTC_Review_实现1对1音视频 </title>
<h1> WebRTC_Review_实现1对1音视频 </h1>
</head>
<body>
<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="main.js"></script>
</body>
</html>
|