一对一WebRTC视频通话系列(二)——websocket和join信令实现

本系列博客主要记录WebRtc实现过程中的一些重点,代码全部进行了注释,便于理解WebRTC整体实现。


一对一WebRTC视频通话系列往期博客:

一对一WebRTC视频通话系列(一)—— 创建页面并显示摄像头画面


一、websocket实现

1.1客户端

main.js文件中,定义了一个名为ZeroRTCEngine的类,该类使用WebSocket进行通信。ZeroRTCEngine类的实例化过程如下:

  1. 首先,声明并定义一个名为zeroRTCEngine的类,接受一个wsUrl参数。
  2. 在ZeroRTCEngine类中,定义一个init方法,用于初始化WebSocket地址。
  3. 定义ZeroRTCEngine类的方法createWebsocket,用于创建WebSocket对象。
  4. createWebsocket方法中,定义多种WebSocket事件处理函数.
  5. 定义onopenonmessageoncloseonerror方法,用于处理WebSocket连接状态和接收到的消息。

在实际使用中,通过调用ZeroRTCEngine类的createWebsocket方法来创建一个WebSocket实例,并监听各种事件(如打开、消息传递、关闭和错误)。

	//定义 ZeroRTCEngine 的 init 方法
	//参数 wsUrl:WebSocket 的地址
	ZeroRTCEngine.prototype.init = function(wsUrl) {
	    //设置 WebSocket url
	    this.wsUrl = wsUrl;
	    this.signaling =null;
	}
	
	//定义 ZeroRTCEngine 的 createWebsocket 方法
	ZeroRTCEngine.prototype.createWebsocket = function() {
	    //创建 WebSocket 对象
	    zeroRTCEngine = this;
	    zeroRTCEngine.signaling = new WebSocket(this.wsUrl);
	
	    //设置打开函数
	    zeroRTCEngine.signaling.onopen = function () {
	        zeroRTCEngine.onopen();
	    }
	
	    //设置关闭函数
	    zeroRTCEngine.signaling.onclose = function (event) {
	        zeroRTCEngine.onclose(event);
	    }
	    //设置错误函数
	    zeroRTCEngine.signaling.onerror = function (event) {
	        zeroRTCEngine.onerror(event);
	    }
	    //设置消息传递函数
	    zeroRTCEngine.signaling.onmessage = function (event) {
	        zeroRTCEngine.onmessage(event);
	    }
	}
	
	
	ZeroRTCEngine.prototype.onopen = function() {
	    console.log("WebSocket connection established.");
	}
	
	ZeroRTCEngine.prototype.onmessage = function(event) {
	    console.log("Received message:" + event.data);
	    //解析收到的消息
	    var message = JSON.parse(event.data);
	}
	
	ZeroRTCEngine.prototype.onclose = function(event) {
	    console.log("WebSocket connection closed." + event.data + ", reason" + EventCounts.reason);
	}
	
	ZeroRTCEngine.prototype.onerror = function(event) {
	    console.log("WebSocket connection error:" + event.data);  
	}
	
	zeroRTCEngine = new ZeroRTCEngine("ws://192.168.3.181:8099");
	zeroRTCEngine.createWebsocket();

1.2服务端

创建一个名为signal_server.jsjs新文件,搭建简单的WebSocket服务器,用于处理客户端连接、消息发送和接收、连接关闭等基本操作。在实际应用中,可能需要根据需求对代码进行修改和扩展。

  1. 使用ws.createServer()方法创建一个WebSocket服务器。这个方法接受一个回调函数作为参数,该回调函数在客户端连接到服务器时被调用。
  2. 在回调函数中,使用console.log()输出一条connection established的消息,表示连接已建立。然后,向客户端发送一条消息,例如"我收到你的连接了"。
  3. 使用conn.on("text", function (str) {...})监听客户端发送的消息。
    当客户端发送消息时,会触发这个回调函数。使用console.info()输出一条消息,表示收到的消息。
  4. 在连接关闭时,使用conn.on("close", function (code, reason) {...})监听连接关闭。
    当客户端关闭连接时,会触发这个回调函数。使用console.log()输出一条消息,表示连接已关闭。
  5. 使用了conn.on("error", function (err) {...})来监听连接错误。当发生错误时,会触发这个回调函数,输出错误信息。
  6. 使用server.listen()方法将服务器绑定到一个指定的端口。在实际应用中,通常需要根据不同的需求来修改这个端口。
	// 引入ws模块
	var ws = require("nodejs-websocket");
	// 定义端口号
	var port =8099;
	
	// 创建一个WebSocket服务器
	var server = ws.createServer(function (conn) {
	    // 连接建立时的输出
	    console.log("New connection");
	    // 向客户端发送消息
	    conn.sendText("我收到你的连接了")
	    // 监听客户端发送的消息
	    conn.on("text", function (str) {
	        console.info("Received msg:"+str);
	    });
	
	    // 连接关闭时的输出
	    conn.on("close", function (code, reason) {
	        console.log("Connection closed,code:" + code + " reason:" + reason);
	    });
	    // 监听连接错误
	    conn.on("error", function (error) {
	        console.error("发生错误:", error);
	    })
	}).listen(port);

在ubuntu中使用下列指令开启服务端:

	node signal_server.js

在这之前切记先用以下两行命令初始化:

	sudo npm init -y
	sudo npm install nodejs-websocket

在这里插入图片描述
在这里插入图片描述

二、join信令实现

2.1 客户端

修改按钮点击事件

document.getElementById('joinBtn').onclick = function () {
    roomId = document.getElementById('roomId').value;
    if(roomId == '' || roomId == "Please enter the room ID"){
        alert('Please enter the room ID');
        return;
    }

    console.log("joinBtn clicked,roomId: " + roomId);
    //初始化本地码流
    initLocalStream();
}

首先创建一个JSON对象,包含命令(cmd)、房间ID(roomId)和用户ID(uid),然后将这个JSON对象转换为字符串。接着,调用zeroRTCEngine.sendMessage方法将这个字符串发送给服务器,表示用户要加入房间。最后,在控制台输出一条消息,表明用户已经成功加入房间。

// join 主动加入房间
// leave 主动离开房间
// new-peer 有人加入房间,通知已经在房间的人
// peer-leave 有人离开房间,通知已经在房间的人
// offer 发送offer给对端peer
// answer发送offer给对端peer
// candidate 发送candidate给对端peer
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);//本地ID   
var remoteUserId = -1; //对方用户ID
var roomId = -1; //房间ID

ZeroRTCEngine.prototype.sendMessage = function(message) {
    this.signaling.send(message);
}

function doJoin(roomId) {
    var jsonMsg = {
        'cmd': 'join',
        'roomId': roomId,
        'uid': localUserId,
    }; 
    var message = JSON.stringify(jsonMsg); //将json对象转换为字符串
    zeroRTCEngine.sendMessage(message);   //设计方法:用实现方法而不是直接用变量    
    console.info("doJoin message: " + message);
}

运行效果:
在这里插入图片描述

2.2 服务端

修改signal_server.js
1.添加相关宏定义,以及WebRTC所需要用到的Map类。

// join 主动加入房间
// leave 主动离开房间
// new-peer 有人加入房间,通知已经在房间的人
// peer-leave 有人离开房间,通知已经在房间的人
// offer 发送offer给对端peer
// answer发送offer给对端peer
// candidate 发送candidate给对端peer
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";


/** ----- ZeroRTCMap ----- */
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) {// equal
                return i;
            }
        }
        return -1;
    };
}

2.定义Client函数,用于初始化用户信息

function Client(uid, conn, roomId) {
    this.uid = uid;     // 用户所属的id
    this.conn = conn;   // uid对应的websocket连接
    this.roomId = roomId;
}

3.处理加入房间请求
监听客户端发送的消息,如果时加入房间的,利用handleJoin()函数进行处理

// 监听客户端发送的消息
    conn.on("text", function (str) {
        console.info("Received msg:"+str);
        var jsonMsg = JSON.parse(str);

        switch(jsonMsg.cmd){
            case SIGNAL_TYPE_JOIN:
                handleJoin(jsonMsg, conn); 
            break;
            
        }
    });

定义handleJoin()函数,处理加入房间请求。它首先获取房间ID用户ID,然后获取房间Map。如果房间不存在,则创建一个新的房间。如果房间已满,给出提示并拒绝加入请求。如果房间未满,创建一个新的客户端并将其添加到房间。如果房间中用户数大于1,则通知房间中的其他用户有新人加入。

实现原理:
1.从传入的messageconn参数中获取房间ID用户ID
2.尝试获取roomId对应的房间Map。如果房间Map不存在,则创建一个新的房间Map。
3.检查房间是否已满。如果已满,给出提示并返回;否则,创建一个新的客户端并将其添加到房间。
4.如果房间中用户数大于1,则遍历房间中的所有用户,通知他们有新人加入。同时,通知自己房间中有新人加入。

function handleJoin(message, conn){
    // 获取房间ID和用户ID
    var roomId = message.roomId;
    var uid = message.uid;
    console.log(" uid:"+uid + "try to join room: "+roomId);

    // 获取房间Map
    var roomMap = roomTableMap.get(roomId);
    // 如果房间不存在,则创建一个新的房间
    if(roomMap == null){
        roomMap = new ZeroRTCMap();
        roomTableMap.put(roomId, roomMap);
    }

    // 如果房间已满,给出提示
    if(roomMap.size() >= 1){
        console.error("roomId:" + roomId + " is full");

        conn.sendText('roomId:' + roomId + ' is full');
        return;
    }

    // 创建一个新的客户端
    var client = new Client(uid, conn, roomId);
    // 将用户添加到房间
    roomMap.put(uid, client);
    // 如果房间中的用户数大于1,则通知房间中的其他用户
    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);

                // 通知自己,房间里有的人
                jsonMsg = {
                    'cmd':SIGNAL_TYPE_RESP_JOIN,
                    'remoteUid':remoteUid
                };
                msg = JSON.stringify(jsonMsg);
                console.info("resp-join"+msg);
                conn.sendText(msg);
            }
        }
    }
}

整体代码:

// 引入ws模块
var ws = require("nodejs-websocket");
// 定义端口号
var port =8099;

// join 主动加入房间
// leave 主动离开房间
// new-peer 有人加入房间,通知已经在房间的人
// peer-leave 有人离开房间,通知已经在房间的人
// offer 发送offer给对端peer
// answer发送offer给对端peer
// candidate 发送candidate给对端peer
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";


/** ----- ZeroRTCMap ----- */
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) {// equal
                return i;
            }
        }
        return -1;
    };
}

var roomTableMap = new ZeroRTCMap();

function Client(uid, conn, roomId) {
    this.uid = uid;     // 用户所属的id
    this.conn = conn;   // uid对应的websocket连接
    this.roomId = roomId;
}

// 处理加入房间请求
function handleJoin(message, conn){
    // 获取房间ID和用户ID
    var roomId = message.roomId;
    var uid = message.uid;
    console.log(" uid:"+uid + "try to join room: "+roomId);

    // 获取房间Map
    var roomMap = roomTableMap.get(roomId);
    // 如果房间不存在,则创建一个新的房间
    if(roomMap == null){
        roomMap = new ZeroRTCMap();
        roomTableMap.put(roomId, roomMap);
    }

    // 如果房间已满,给出提示
    if(roomMap.size() >= 1){
        console.error("roomId:" + roomId + " is full");

        conn.sendText('roomId:' + roomId + ' is full');
        return;
    }

    // 创建一个新的客户端
    var client = new Client(uid, conn, roomId);
    // 将用户添加到房间
    roomMap.put(uid, client);
    // 如果房间中的用户数大于1,则通知房间中的其他用户
    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);

                // 通知自己,房间里有的人
                jsonMsg = {
                    'cmd':SIGNAL_TYPE_RESP_JOIN,
                    'remoteUid':remoteUid
                };
                msg = JSON.stringify(jsonMsg);
                console.info("resp-join"+msg);
                conn.sendText(msg);
            }
        }
    }
}

// 创建一个WebSocket服务器
var server = ws.createServer(function (conn) {
    // 连接建立时的输出
    console.log("New connection");
    // 向客户端发送消息
    conn.sendText('我收到你的连接了')
    // 监听客户端发送的消息
    conn.on("text", function (str) {
        console.info("Received msg:"+str);
        var jsonMsg = JSON.parse(str);

        switch(jsonMsg.cmd){
            case SIGNAL_TYPE_JOIN:
                handleJoin(jsonMsg, conn); 
            break;
            
        }
    });
    
    // 连接关闭时的输出
    conn.on("close", function (code, reason) {
        console.log("Connection closed,code:" + code + " reason:" + reason); 
    });
    // 监听连接错误
    conn.on("error", function (error) {
        console.error("发生错误:", error);
    })
}).listen(port);

运行效果:

客户1:
第一个创建,刚开始没有红框的内容。在客户2新加入后,收到服务端发来的信令。
在这里插入图片描述

客户2:
创建后,会收到服务端发来的,房间内已有的客户1的信息。
在这里插入图片描述
客户3:
房间内已经有2个客户了,会提示房间已满,无法加入房间。
在这里插入图片描述
服务端:
在这里插入图片描述

  • 40
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要使用 webRTC 进行一对一视频通话,需要以下步骤: 1. 建立本地媒体流对象 使用 getUserMedia() 方法获取本地视频和音频流对象。 ```javascript navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(function(localStream) { // localStream 是本地媒体流对象 }) .catch(function(error) { console.log(error); }); ``` 2. 建立 peer connection 对象 使用 RTCPeerConnection() 方法创建 peer connection 对象,并将本地媒体流对象添加到 peer connection 中。 ```javascript var pc = new RTCPeerConnection(); localStream.getTracks().forEach(function(track) { pc.addTrack(track, localStream); }); ``` 3. 建立远程媒体流对象 通过 peer connection 对象,监听远程的媒体流对象。 ```javascript pc.addEventListener('track', function(event) { // event.streams 是一个媒体流对象数组 var remoteStream = event.streams[0]; }); ``` 4. 发送 offer 和 answer 通过 peer connection 对象,发送 offer 和 answer 信令,建立连接。 ```javascript pc.createOffer() .then(function(offer) { return pc.setLocalDescription(offer); }) .then(function() { // 发送 offer 信令 }) .catch(function(error) { console.log(error); }); // 接收到 offer 信令后,回复 answer 信令 pc.setRemoteDescription(offer) .then(function() { return pc.createAnswer(); }) .then(function(answer) { return pc.setLocalDescription(answer); }) .then(function() { // 发送 answer 信令 }) .catch(function(error) { console.log(error); }); // 接收到 answer 信令后,设置远程描述 pc.setRemoteDescription(answer) .catch(function(error) { console.log(error); }); ``` 5. 连接成功后,进行视频通话 当连接成功后,就可以在页面中显示本地视频和远程视频,并进行视频通话了。 ```javascript // 添加本地视频和远程视频 var localVideo = document.getElementById('localVideo'); var remoteVideo = document.getElementById('remoteVideo'); localVideo.srcObject = localStream; remoteVideo.srcObject = remoteStream; ``` 完整的一对一视频通话示例代码请参考:https://webrtc.github.io/samples/src/content/peerconnection/pc1/

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君莫笑lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值