nodjs+soket.io+webRTC实现聊天室实录
在家闲着没事,就把之前写的web聊天室分享一下吧,其实早就想发了,但懒惰使我无力,而且没啥动力,哈哈,觉得对自己有帮助的朋友点个赞噢,菜鸡需要鼓励!
额,思路不太清晰,建议先下载源代码看看:CSDN资源,百度云下载 (提取码: mr43)
一、HTML界面实现
首先要说一下这个界面我是引用的别人模板,我是在这个模板的基础上进行增删改的,但惭愧的是我已经不知道是从哪位大佬哪里copy的了,找了半天也没找着,知道的网友可以跟我说说,我注明一下。
1. 效果图
2. 整体代码
<html>
<head>
<title>chat UI</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="/css/chatroom.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/css/jquery.dialogbox.css">
<script>
var user_name="";
do user_name=prompt("请输入你的昵称:");
while (user_name==""||user_name==undefined||user_name==null);
</script>
</head>
<script>
</script>
<body class="box">
<div id="btn-dialogBox"></div>
<div id="auto-dialogBox"></div>
<div class="video_bar">
<canvas id="pic_drawer" hidden></canvas>
<video id="video_me" autoplay hidden></video>
<video id="video_p2p" autoplay hidden></video>
<ul id="video_list">
<!-- 视频窗口放置处 -->
<!-- <li>-->
<!-- <video id="video_p2p1" autoplay></video>-->
<!-- <span>猪猪侠</span>-->
<!-- </li>-->
<!-- <li>-->
<!-- <video id="video_p2p2" autoplay></video>-->
<!-- <span>猪猪侠</span>-->
<!-- </li>-->
</ul>
</div>
<div class="container">
<div class="chatbox">
<div class="chatleft">
<div class="top">
<i class="fas fa-bars" style="font-size: 1.4em"></i>
<!-- <input type="text" placeholder="search" style="width: 140px; height: 36px; margin-left: 25px;">-->
<!-- <button class="searchbtn">搜</button>-->
在线人数:<span id="user_num" style="color: red">0</span>  昵称:<span id="user_name" style="color: green"></span>
</div>
<div class="center">
<ul id="multi_chat_option" style="background-color: #d7dede;"><img ><span>群聊天室</span></ul>
<ul id="user_list">
<!-- 用户列表 -->
<!-- <li><img><span>小兰</span></li>-->
<!-- <li><img><span>三大哈</span></li>-->
</ul>
</div>
</div>
<div class="chatright">
<div class="top">
<div class="top_chatting">
<img src="/img/user_log.png">
<span id="chat_user_name" style="margin-left: 20px;">聊天大厅</span>
</div>
<i class="fas fa-ellipsis-v" style="font-size: 1.4em; position: absolute; right: 20px; color: gray;"></i>
</div>
<div class="center" id="chat_list_scroll">
<ul id="chat_list">
<!-- 聊天内容-->
<!-- <li class="msgleft"><div id="xiaolan"><img>小兰是憨憨j</div><p>This message on the left!</p></li>-->
<!-- <li class="msgright"><p>This message on the right!s a long message! This is a long message! This is a long left m</p><img></li>-->
<!-- <li class="msgleft"><div><img>小红</div><p>This is a long message! This is a long message! This is a long left message!</p></li><li class="msgright"><p>This on the right!</p><img></li>-->
<!-- <li class="msgleft"><div><img>猪大侠</div><p>This is a long message! This is a long message! This is a long left message!</p></li>-->
</ul>
</div>
<div class="footer">
<div class="more_btn">
<!-- <img title="表情" id="emojiBtn" src="/img/emoji.png">-->
<img title="图片上传" id="uploadBtn" onclick="picClick()" src="/img/picture.png">
<ul class="btn_choose">
<input type="file" id="image_file_sel" accept = 'image/*' style="width: 70px">
<li id="quitPicBt" style="display: none"><button onclick="quitPicClick()">结束拍照</button></li>
<li><button onclick="catPicClick()">拍照上传</button></li>
</ul>
<!-- <img title="文件上传" id="fileBtn" src="/img/file.png">-->
<img title="视频聊天" id="videoBtn" onclick="videoClick()" src="/img/video.png">
<!-- <img title="语音聊天" οnclick="testRun()" id="audioBtn" src="/img/audio.png">-->
</div>
<textarea id="inputMsg" onkeyup="onInputEvent(event.key);" maxlength="800" rows="5" cols="40" style="width: 100%; resize: none; border: none; " placeholder="请在此输入要发送的内容..."></textarea>
<button class="sendbtn" onclick="sendMsg()">发送</button>
</div>
</div>
</div>
</div>
<script src="/js/jquery-3.5.1.min.js"></script>
<script src="/js/jquery.dialogBox.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/js/socket_setting.js"></script>
<script src="/js/chatroom.js"></script>
<script src="/js/bbyrtc.js"></script>
</body>
</html>
3. 解释
这里我有引用很多文件,在head里是两个css文件:
<!-- css布局文件-->
<link href="/css/chatroom.css" rel="stylesheet">
<!-- dialog插件的css文件-->
<link rel="stylesheet" type="text/css" href="/css/jquery.dialogbox.css">
其中,
‘chatroom.css’
是我的界面的的布局,没有啥好说的,而‘jquery.dialogbox.css’
则是我引入的一个插件,因为在后面的开发中,需要服务器与用户进行交互,这就要用到询问对话框,当然也可以使用DOM
Window 的confirm()
函数,但那样的话,在有的浏览器上可能会不支持。就比如谷歌,在谷歌浏览器上使用
confirm(),它貌似会说这玩意必须是由用户引起的事件触发,服务器发送信息触发就报错。
因此我在网上搜了个dialog插件放了进去,瞬间就舒服了。css就不贴了,太长了,在资源里。
然后就是body里面用到的js文件了:
<!-- jquery不必多说,js与jq的配合,舒服-->
<script src="/js/jquery-3.5.1.min.js"></script>
<!-- dialog插件主程序-->
<script src="/js/jquery.dialogBox.js"></script>
<!-- 引用socket.io,必须的 -->
<script src="/socket.io/socket.io.js"></script>
<!-- 这里存放了我对socket事件的处理等等 -->
<script src="/js/socket_setting.js"></script>
<!-- 这个文件主要是界面事件的监听,如按钮,鼠标点击等-->
<script src="/js/chatroom.js"></script>
<!-- 实现RTC视频聊天的文件-->
<script src="/js/bbyrtc.js"></script>
因为代码太多,我就堆js文件进行了分类,按大致情况将他们丢在三个不同文件里,也方便修改哈,具体内容在后面解读!
二、关键代码实现
四个js文件,一个css文件 加起来七八百行了,为避免影响阅读就不一一贴上了,这里例举几个关键代码块吧。
1. 特殊部分
(1) 图片按钮鼠标移动变色(css)
.more_btn img:hover{/*鼠标放上去*/
filter:alpha(Opacity=40);
-moz-opacity:0.4;
opacity: 0.4;
}
效果演示:
(2)图片传输
function readFileAsDataURL(file,onload) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = onload;
reader.onerror = function() {
console.log('Unable to read ' + file.fileName);
};
}
document.getElementById('image_file_sel').onchange=function(e){
console.log('选择文件');
readFileAsDataURL(this.files[0],function (event) {
iosocket.emit('msg_to',{data: event.target.result,type:'img',room:my_room});
addMsgPic(null,null,event.target.result);
});
$(".btn_choose").hide();
}
这里嘛,是将选择的图片转化为
DataURL
,就是字符串形式,然后发送出去,不过这里与其它部分有点不同,readAsDataURL()
不会马上结束,js没有sleep()函数让我们等待,也不能等待,于是只能将发送代码进行封装,等readAsDataURL结束后自动调用,这里为了以后复用,我将函数当成变量传送(菜鸡不知专业术语,见谅)。DataURL是将图片镶入html的不错选择。
2. js界面部分
(1) 几种页面监听事件
1) 直接在html标签中声明onclick
<img title="视频聊天" id="videoBtn" onclick="videoClick()" src="/img/video.png">
2)JQ选择器.click()
//点击事件
$('#multi_chat_option').click(function () {
if(chatting_user!='multi_chat_option'){
$('#btn-dialogBox').dialogBox({//询问一下是否进入私聊
hasBtn: true,hasClose:true, confirmValue:'确认',cancelValue: '算了',
title: '通知', content: "你正在和"+user_list[chatting_user]+"私聊,确定退出嘛?",
confirm:function () {
iosocket.emit('chat_p2p',{what:'leave_out'});
}
});
}
// $
- JQ选择器.on()
//移动变色事件
$('#user_list').on('mouseenter','li',function () {
if(this.id==chatting_user) return;
this.style.backgroundColor='#d7dede';
});
$('#user_list').on('mouseout','li',function () {
if(this.id==chatting_user) return;
this.style.backgroundColor='white';
});
(2)dialog插件的引用
$('#btn-dialogBox').dialogBox({//询问一下是否进入私聊
hasBtn: true,hasClose:true, confirmValue:'确认',cancelValue: '算了',
title: '通知', content: "你正在和"+user_list[chatting_user]+"私聊,确定退出嘛?",
confirm:function () {
iosocket.emit('chat_p2p',{what:'leave_out'});
}
});
(3)用户列表的显示与刷新
function addUserLi(id,name){
let user_h='<li class="user_li" id="'+id+'">'+'<img/><span>'+name+'</span></li>';
$('#user_list').append($(user_h));
}
iosocket.on('flash list', function(msgs) {//刷新用户列表,列表中id用socket.id
$('#user_list').empty();//先清空
user_list=msgs.list;
my_room=msgs.room;
delete msgs.list[iosocket.id];//删除自己的记录,如果有的话
// addUserLi(iosocket.id,user_name);//顶部是群聊
for(let key in msgs.list) addUserLi(key,msgs.list[key]);
$('#user_num').text(msgs.num);
});
iosocket.on('news', function(msgs) {//用户变更
if(msgs.what=="new") {//新用户加入聊天室
if($('#' + msgs.id).length == 0){
addUserLi(msgs.id, msgs.name);
user_list[msgs.id]=msgs.name;
}
} else{//有用户退出聊天室
$('#' + msgs.id).remove();
if(isInVideo){
let v=$('#video_' + msgs.id);
if (v) {
v.parent().remove();
pcs[msgs.id].close();
delete pcs[msgs.id];
}
}
delete user_list[msgs.id];
}
$('#user_num').text(msgs.num);//更新人数显示
});
(4)聊天记录显示
function addMsgPic(id,name,src) {//显示接收到的图片
$('#chat_list').append(
(id==null)? '<li class="msgright"><p><img class="msg_pic" src="'+src+'"></p><img></li>'
:'<li class="msgleft"><div id='+id+'><img>'+name+'</div><p><img class="msg_pic" src="'+src+'"></p></li>'
);
let scl =$('#chat_list_scroll');
scl.animate({scrollTop: scl.get(0).scrollHeight},500);
}
function addMsgRecord(id,name,msg) {//显示收到的消息
let htext=(id==null)? '<li class="msgright"><p>'+msg+'</p><img></li>'
: '<li class="msgleft"><div class="chatRecd_'+id+'"><img>'+name+'</div> <p>'+msg+'</p></li>';
$('#chat_list').append(htext);
let scl =$('#chat_list_scroll');
scl.animate({scrollTop: scl.get(0).scrollHeight},500);//移动到最新消息
}
(4)新建视频聊天窗口
function createRemoteVideo(id) {
let remoteVideo=document.getElementById('video_'+id);
if (remoteVideo) return remoteVideo;
video_list.append('<li><video autoplay id="video_'+id+'"></video><span>'+user_list[id]+'</span></li>');
console.log('新建 '+user_list[id]+' 的视频窗口!');
return remoteVideo=document.getElementById('video_'+id);
}
3. socket.io 主要代码
聊天室用socket.io是非常不错的选择,这里是对socket_setting.js的几个监听事件的解释
(1)connect 事件
iosocket.emit('set nickname',user_name);
iosocket.on(.....);
...
这是socket.io成功建立建立后触发的事件,既然建立连接成功,辣么就该初始化其它事件了,同时告诉服务器,我丫来了,记住俺的名字,user_name!
(2)webrtc 事件
这是用来进行视频聊天的,当用户收到一个sdp后,判断这个
sdp
是offer
还是answer
。
- 如果收到的是
offer
,初始化并发送answer- 如果收到的是
answer
,将sdp放在自己的RemoteDescription
里,准备通话了要 如果收到的不是sdp而是candidate
的话,辣么就直接将它交给ICE
就成了。
(3)flash list 和 news 事件
这里前面说了,这是刷新用户列表用的。
(4)chat_p2p 事件
这个是私聊请求等互动事件的处理,没啥好说的。
(5)msg 事件
iosocket.on('msg', function(msgs) {
if(msgs.type=='img') addMsgPic(msgs.id,msgs.name,msgs.data);
else addMsgRecord(msgs.id,msgs.name,msgs.msg);
});
显示聊天记录用的。
(6) video_multi 事件
iosocket.on('video_multi', function(data) {
if(data.isNew){//有人新进入视频,直接向他发送offer
console.log(user_list[data.id]+'加入群视频!');
createRTCPeerConnection(data.id,createRemoteVideo(data.id),true);
}else if(data.isQuit) {//有人退出
console.log(user_list[data.id]+'退出群视频!');
pcs[data.id].close();
delete [data.id];
$('#video_'+data.id).parent().remove();
}else {//list,自己嘛什么都不干。
console.log('已加入!');
if(data.list.length<2)
$('#btn-dialogBox').dialogBox({//询问一下
title: '通知', content: "暂时没有人加入群视频,请等待?",
hasClose:true, cancelValue:'哦哦'
});
isInVideo=true;
}
});
群聊时的视频聊天请求的处理,有人请求视频聊天,同意后会新建个
RTCPeerConnection
,就这样。
3. webRTC 主要代码
webRTC相关的代码并不多,毕竟我只实现了视频聊天,其它的什么优化视频啊什么的骚操作一个木有!
这里我将创建webRTC对象封装成了函数,调用这个函数就能获得一个
PeerConnection
,这个PeerConnection将储存在变量pcs[user_id]
中,其中,pcs
是一个数组,user_id
是与自己建立PeerConnection连接的用户的socket连接的id,用于区分识别用户。
注:在创建 PeerConnection 时,要分情况,若自己是接收方的话,则不在这里添加视频流track。而是在添加了对方的offer信息后将视频流添的track加进去。如下图。
3. server 服务端主要代码
服务端主要就是信息转发,没啥好说的,这里我用的是https,至于怎么配置,嗯,在我之前发的另一篇文中。。。
三、总结
差不多就这样了,这此学习还是挺成功的,虽然有很多计划的功能都没有实现(皮一下)。毕竟在此之前我只是处于认得出这是html语言的阶段,哈哈,有的夸张了,不过也八九不离十就那样。学习期间很感谢助教大大的帮助,其它的,感谢网友分享的各种资源吧!如果本文对网友们的学习有帮助,点个赞呗,有错误也请指出(认真脸)。