webrtc视频聊天项目

1.环境准备

windows webstorm环境和ubuntu云服务器通过SFTP进行连接,都要进行安装(根据如下命令):

① npm install serve-index

② npm install log4js

③ npm install [email protected]

④ npm install express

⑤ npm install [email protected]

用Google浏览器

2.搭建server引入模块

"use strict"

var http = require("http")
var https = require("https")
var fs = require("fs")
var express = require("express")
var serveIndex = require("serve-index")
var log4js = require("log4js")
var socketIO = require("socket.io")
var logger = log4js.getLogger()
logger.level = "info"

var app = express()
app.use(serveIndex("./www"))
app.use(express.static("./www"))

var httpServer = http.createServer(app)
.listen(8888, "0.0.0.0")

var options = {
key: fs.readFileSync("./cert/ssl_server.key"),
cert: fs.readFileSync("./cert/ssl_server.crt")
}

var httpsServer = https.createServer(options, app)
.listen(443, "0.0.0.0")

3.html用户注册登陆界面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myVideoChat</title>
</head>
<body>
<div align="center">
<table>
<tr><td>
<label>用户名:</label>
<input id="username">
</td></tr>
<tr><td>
<label>密码:</label>
<input id="pwd" type="password">
</td></tr>
<tr><td align="center">
<button id="register" style="font-size: 20px">注册</button>
<button id="login" style="font-size: 20px">登陆</button>
</td></tr>
</table>
</div>
</body>
</html>

 4.使用数据库sqlite

var sqlite3 = require("sqlite3")
var db = null
var sql = ""

db = new sqlite3.Database("app.db", e=>{
if (e)
logger.info(e)
else
logger.info("suc:" + e)
})

db.run("create table if not exists users(id integer primary key autoincrement, name char(50) unique, pwd char(200))", e=>{
if (e)
logger.info(e)
else
logger.info("create table users successfully")
})

sql = "insert into users(name, pwd) values('haha', '123')"
db.exec(sql, e=>{
if (e)
logger.info(e)
else
logger.info("insert into users successfully")
})
sql = "select id, name, pwd from users"
db.all(sql, (e, rows)=>{
if (e)
logger.info(e)
else
logger.info(rows)
})

5.登陆界面的socket.io基于事件的实时双向通信

server端:

var io = socketIO.listen(httpsServer)
io.sockets.on("connection", socket=>{
logger.info("connect:"+socket.id)

//监听信号
socket.on("login", (uname, pwd)=>{
db.all("select id from users where name=? and pwd=?", [uname, pwd], (e, rows)=>{
if (e) {
logger.info(e)
socket.emit("servererr")
} else {
if (rows.length === 1)
socket.emit("logininsuccess", uname)
else
socket.emit("loginerr")
}
})
})
})

client端:

var socket = null
function start() {
socket = io.connect()

//监听来自服务器的消息
socket.on("servererr", ()=>{
alert("服务器异常,请重试")
})
socket.on("logininsuccess", ()=>{
alert("登录成功")
})
socket.on("loginerr", ()=>{
alert("用户名或密码错误")
})
}

start()

function login() {
var uname = unameIpt.value.trim()
var pwd = pwdIpt.value.trim()

if (uname === "" || pwd === "") {
alert("请输入用户名和密码")
return
}

socket.emit("login", uname, pwd)
}

loginBtn.onclick = login

 6.html注册界面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myVideoChat</title>
</head>
<body>
<div align="center">
<h1>欢迎到注册界面</h1>
<table>
<tr><td align="right">
<label>用户名:</label>
<input id="uname">
</td></tr>
<tr><td align="right">
<label>密码:</label>
<input id="pwd" type="password">
</td></tr>
<tr><td align="right">
<label>再次确认:</label>
<input id="ppwd" type="password">
</td></tr>
<tr><td align="right">
<button id="back">返回</button>
<button id="register">注册</button>
</td></tr>
</table>
</div>

<script src="./js/socket.io.js"></script>
<script src="./js/register.js"></script>
</body>
</html>

按钮事件进入到注册界面

registerBtn.onclick = ()=>{
window.location.href = "../register.html"
}

7.注册界面的socket.io基于事件的实时双向通信

server端:

socket.on("register", (uname, pwd)=>{
db.all("select id from users where name=?", uname, (e, rows)=>{
if (e) {
logger.info(e)
socket.emit(err)
} else {
if (rows.length === 1) {
socket.emit("samename")
} else {
db.run("insert into users(name, pwd) values(?,?)", [uname, pwd], e=>{
if (e) {
logger.info(e)
socket.emit("servererr")
} else {
socket.emit("registerok", uname)
}
})
}
}
})
})

client端:

"use strict"

var unameIpt = document.querySelector("input#uname")
var pwdIpt = document.querySelector("input#pwd")
var ppwdIpt = document.querySelector("input#ppwd")
var backBtn = document.querySelector("button#back")
var registerBtn = document.querySelector("button#register")

var socket = null
function start() {
socket = io.connect()
socket.on("samename", ()=>{
alert("该用户已被注册")
})
socket.on("servererr", ()=>{
alert("服务器操作错误,请联系系统管理员")
})
socket.on("registerok", ()=>{
alert("注册成功,返回主界面")
goback()
})
}

start()

function goback() {
history.back()
}

backBtn.onclick = goback

function register() {
var uname = unameIpt.value.trim()
var pwd = pwdIpt.value.trim()
var ppwd = ppwdIpt.value.trim()

if (uname === "" || pwd === "" || ppwd === "" || pwd !== ppwd) {
alert("请输入用户名,并输入两次相同密码")
return
}

socket.emit("register", uname, pwd)
}

registerBtn.onclick = register

8.html聊天界面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myVideoChat</title>
</head>
<body>
<div align="center">
<h1 id="welcome">欢迎来到聊天室:</h1>
<table>
<tr><td align="right">
<label id="users">当前在线人数:</label>
<label>当前位置:用户大厅</label>
<button id="exit">退出大厅</button>
</td></tr>
<tr><td>
<textarea id="msglist" rows="20" style="width: 300px"></textarea>
</td></tr>
<tr><td>
<input id="msg" style="width: 66%">
<button id="send" style="width: 28%">发送</button>
</td></tr>
<tr><td>
<button id="videoRoom" style="width: 100%">进入1v1聊天室</button>
</td></tr>
</table>
</div>

<script src="js/socket.io.js"></script>
<script src="js/chat.js"></script>
</body>
</html>

9.聊天界面的socket.io基于事件的实时双向通信

server端:

socket.on("cjoin", (room, uname)=>{
logger.info("cjoin:", room, uname)
socket.join(room)
var myRoom = io.sockets.adapter.rooms[room]
var users = Object.keys(myRoom.sockets).length
logger.info("房间中有:" + users + "人")
socket.emit("cjoinsuccess", room, users)
socket.to(room).emit("cotherjoined", room, uname, users)
})

socket.on("cexit", (room, uname)=>{
var myRoom = io.sockets.adapter.rooms[room]
var users = Object.keys(myRoom.sockets).length - 1
socket.leave(room)
socket.emit("cexited")
socket.to(room).emit("cotherexited", uname, users)
})

socket.on("cmsg", (room, uname, msg)=>{
io.in(room).emit("cgetmsg", uname, msg)
})

client端:

 "use strict"

var url = window.location.href

var uname = url.split("?")[1].split("=")[1]

var welcomeH1 = document.querySelector("h1#welcome")
welcomeH1.textContent = "欢迎来到聊天室:" + uname

var exitBtn = document.querySelector("button#exit")
var msglistTxt = document.querySelector("textarea#msglist")
var msgIpt = document.querySelector("input#msg")
var sendBtn = document.querySelector("button#send")
var videoRoomBtn = document.querySelector("button#videoRoom")
var userlabel = document.querySelector("label#users")

var room = "defaultRoom"

var socket = null
function start() {
socket = io.connect()

socket.emit("cjoin", room, uname)

socket.on("cjoinsuccess", (room, users)=>{
userlabel.textContent = "当前在线人数:" + users
msglistTxt.value = "欢迎" + uname + "进入房间\n"
})

socket.on("cotherjoined", (room, uname, users)=>{
msglistTxt.value += "欢迎" + uname + "进入房间\n"
})

socket.on("cexited", ()=>{
history.back()
})

socket.on("cotherexited", (uname, users)=>{
msglistTxt.value += uname + "离开了房间\n"
userlabel.textContent = "当前在线人数:" + users
})

socket.on("cgetmsg", (uname, msg)=>{
msglistTxt.value += uname + ":" + msg + "\n"
})
}

start()

exitBtn.onclick = ()=>{
socket.emit("cexit", room, uname)
}

function sendMsg(){
var msg = msgIpt.value.trim()
if (msg === "") return

socket.emit("cmsg", room, uname, msg)
msgIpt.value = ""
}

sendBtn.onclick = sendMsg

msgIpt.onkeydown = e=>{
var event = window.event || e
if (event.keyCode === 13)
sendMsg()
}

10.html视频界面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>myVideoChat</title>
</head>
<body>
<div align="center">
<h1 id="welcome">欢迎到1v1视频聊天界面</h1>
<table>
<tr><td colspan="2">
<input id="room">
<button id="enterRoom">进入房间</button>
<button id="leaveRoom">离开房间</button>
</td></tr>
<tr>
<td><label>本地视频</label></td>
<td><label>远程视频</label></td>
</tr>
<tr>
<td><video id="localVideo" autoplay playsinline></video></td>
<td><video id="remoteVideo" autoplay playsinline></video></td>
</tr>
</table>
</div>

<script src="js/socket.io.js"></script>
<script src="js/videoRoom.js"></script>
</body>
</html>

11. 视频界面的socket.io基于事件的实时双向通信

server端:

socket.on("vjoin", (room, uname)=>{
logger.info("vjoin:", room, uname)
socket.join(room)
var myRoom = io.sockets.adapter.rooms[room]
var users = Object.keys(myRoom.sockets).length
if (users > 2) {
socket.leave(room)
socket.emit("vfull", room)
} else {
socket.emit("vjoined", room)
if (users > 1) {
socket.to(room).emit("votherjoined", room, uname)
}
}
})

socket.on("vdata", (room, data)=>{
socket.to(room).emit("vgetdata", room, data)
logger.info("vdata:", room, data)
})

socket.on("vleave", (room, uname)=>{
try {
var myRoom = io.sockets.adapter.rooms[room]
var users = Object.keys(myRoom.sockets).length - 1
} catch (ex) {
logger.info(ex)
return
}

socket.leave(room)
logger.info("vleave users=" + users)
socket.emit("vleft", room)
socket.to(room).emit("votherleft", room, uname)
})

client端:

"use strict"

var url = location.href
var uname = url.split("?")[1].split("=")[1]

var welcomeH1 = document.querySelector("h1#welcome")
welcomeH1.textContent = "欢迎到1v1视频聊天界面:" + uname

var roomIpt = document.querySelector("input#room")
var enterRoomBtn = document.querySelector("button#enterRoom")
var leaveRoomBtn = document.querySelector("button#leaveRoom")
var localVideo = document.querySelector("video#localVideo")
var remoteVideo = document.querySelector("video#remoteVideo")

var localStream = null
var remoteStream = null
var socket = null
var room = null
var state = "init"
var pc = null

function start() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert("rtc not supported")
return
}

var constraints = {
video: true,
audio: true
}

navigator.mediaDevices.getUserMedia(constraints)
.then(getStream)
.catch(handleErr)

conn()
}

start()

function getOffer(desc) {
pc.setLocalDescription(desc)
sendMessage(desc)
}

function negotiate() {
if (state === "joined_conn") {
if (pc) {
var options = {
offerToReceiveVideo: true,
offerToReceiveAudio: true
}
pc.createOffer(options)
.then(getOffer)
.catch(handleErr)
}
}
}

function conn() {
socket = io.connect()
//监听消息
socket.on("vjoined", room=>{
alert("成功加入房间:" + room)
state = "joined"
createPeerConnection()
console.log("vjoined:", state)
})

socket.on("votherjoined", (room, uname)=>{
console.log("别人也进来了:" + uname)
if (state === "joined_unbind") {
createPeerConnection()
}
state = "joined_conn"
//媒体协商
negotiate()
console.log("votherjoined state=", state)
})

socket.on("vfull", room=>{
console.log("房间已满:" + room)
state = "left"
alert("房间已满:" + room)
console.log("vfull:", state)
})

socket.on("vgetdata", (room, data)=>{
console.log(data)
if (!data)
return
if (data.type === "candidate") {
console.log("get candidate")
var cddt = new RTCIceCandidate({
sdpMLineIndex: data.label,
candidate: data.candidate
})

pc.addIceCandidate(cddt)
} else if (data.type === "offer") {
console.log("other offer")
pc.setRemoteDescription(new RTCSessionDescription(data))
pc.createAnswer()
.then(getAnswer)
.catch(handleErr)
} else if (data.type === "answer") {
console.log("other answer")
pc.setRemoteDescription(new RTCSessionDescription(data))
} else {
console.log("err message")
}
})

socket.on("vleft", (room)=>{
console.log("离开房间:" + room)
state = "left"
console.log("vleft:", state)
})

socket.on("votherleft", (room, uname)=>{
console.log("别人离开了房间:"+ uname)
state = "joined_unbind"
closePeerConnection()
console.log("votherleft", state)
})
}

function getAnswer(desc) {
pc.setLocalDescription(desc)
sendMessage(desc)
}

function closePeerConnection() {
console.log("close closePeerConnection")
if (pc) {
pc.close()
pc = null
}
}

function createPeerConnection() {
if (!pc) {
var pcConfig = {
"iceServers:":[{
"urls": "turn:111.67.195.158:3478",
"username": "trafalgar",
"credential": "123456"
}]
}

pc = new RTCPeerConnection(pcConfig)
pc.onicecandidate = (e)=>{
if (e.candidate) {
console.log("Candidate:", e.candidate)
sendMessage({
type: "candidate",
label: e.candidate.sdpMLineIndex,
id: e.candidate.sdpMid,
candidate: e.candidate.candidate
})
}
}

pc.ontrack = (e) => {
remoteStream = e.streams[0]
remoteVideo.srcObject = remoteStream
}
}

if (localStream) {
localStream.getTracks().forEach(track=>{
pc.addTrack(track, localStream)
})
}

}

function sendMessage(data) {
if (socket) {
socket.emit("vdata", room, data)
}
}

function getStream(stream) {
localStream = stream
localVideo.srcObject = stream
}

function enterRoom() {
room = roomIpt.value.trim()
if (room === "") {
alert("请输入房间号")
return
}

socket.emit("vjoin", room, uname)
}

function leaveRoom() {
socket.emit("vleave", room, uname)
closePeerConnection()
}


function handleErr(err) {
console.log(err)
}

enterRoomBtn.onclick = enterRoom
leaveRoomBtn.onclick = leaveRoom

12.关于stun/turn服务器验证

Trickle ICE

13.md5加密

<script src="./js/md5.js"></script>

var secpwd = hex_md5(pwd)

socket.emit("login", uname, secpwd)

14.解决中文字错误显示问题


window.location.href = "../chat.html?uname=" + encodeURI(uname)

var uname = decodeURI(url.split("?")[1].split("=")[1])

window.location.href = "../videoRoom.html?uname=" + encodeURI(uname)

15.进行html优化

使用bootstrap

①登陆界面:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<ul class="breadcrumb">
<li><a href="#">登陆</a></li>
</ul>
<div class="container text-center">
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<img src="img/slm.jpg" style="width: 300px; height: 200px">
</div>
</div>
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<!-- <label>用户名:</label>-->
<img src="img/_User.png" style="width: 25px; height: 25px">
<input id="username">
</div>
</div>
<div class="row" style="margin-bottom: 10px">
<div class="col-lg-12">
<!-- <label>密码:</label>-->
<img src="img/password.png" style="width: 20px; height: 20px">
<input id="pwd" type="password">
</div>
</div>
<div class="row">
<div class="col-lg-12" align="center">
<button id="register" button type="button" class="btn btn-warning" style="font-size: 20px; margin-right: 20px">注册</button>
<button id="login" button type="button" class="btn btn-success" style="font-size: 20px">登陆</button>
</div>
</div>
</div>

<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>

②注册界面:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<ul class="breadcrumb">
<li><a href="index.html">登陆</a></li>
<li><a href="#">注册</a></li>
</ul>
<div class="container text-center">
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<!-- <h1>注册界面</h1>-->
<img src="img/sailimu.jpg" style="height: 200px; width: 300px">
</div>
</div>
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<img src="img/_User.png" style="width: 25px; height: 25px">
<!-- <label>用户名:</label>-->
<input id="uname">
</div>
</div>
<div class="row" style="margin-bottom: 10px">
<div class="col-lg-12">
<!-- <label>密码:</label>-->
<img src="img/password.png" style="width: 20px; height: 20px">
<input id="pwd" type="password">
</div>
</div>
<div class="row" style="margin-bottom: 10px">
<div class="col-lg-12">
<img src="img/password.png" style="width: 20px; height: 20px">
<!-- <label>再次确认:</label>-->
<input id="ppwd" type="password">
</div>
</div>
<div class="row" >
<div class="col-lg-12" align="center">
<button id="back" button type="button" class="btn btn-warning" style="font-size: 20px; margin-right: 20px">返回</button>
<button id="register" button type="button" class="btn btn-success" style="font-size: 20px">注册</button>
</div>
</div>
</div>

<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>

③聊天界面:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<ul class="breadcrumb">
<li><a href="index.html">登陆</a></li>
</ul>
<div class="container text-center">
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<h1 id="welcome">欢迎来到聊天室:</h1>
</div>
</div>

<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<label id="users">当前在线人数:</label>
<label>当前位置:用户大厅</label>
<button id="exit" type="button" class="btn btn-warning">退出大厅</button>
</div>
</div>
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<textarea id="msglist" rows="10" style="width: 300px" disabled="true"></textarea>
</div>
</div>
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<input id="msg" style="width: 36%">
<button id="send" type="button" class="btn btn-info">发送</button>
</div>
</div>
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<div class="col-lg-12">
<button id="videoRoom" type="button" class="btn btn-success" style="width: 50%">进入1v1聊天室</button>
</div>
</div>
</div>

<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>

④视频界面:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<div class="container text-center">
<div class="row" style="margin-top: 20px; margin-bottom: 10px">
<h1 id="welcome">欢迎到1v1视频聊天界面</h1>
</div>

<div class="row" style="margin-bottom: 10px">
<div class="col-lg-12">
<tr><td colspan="3">
<input id="room">
<button id="enterRoom" button type="button" class="btn btn-success">进入房间</button>
<button id="leaveRoom" button type="button" class="btn btn-warning">离开房间</button>
</td></tr>
</div>
</div>
<div class="row" style="margin-bottom: 10px">
<div class="col-lg-12">
<label style="margin-right: 300px">本地视频</label>
<label style="margin-left: 300px">远程视频</label>
</div>
</div>
<table border=0 cellspacing=0 cellpadding=0 style="width:100%">
<tr><td style="width: 100%">
<video id="localVideo" autoplay playsinline></video>
</td><td >
<video id="remoteVideo" autoplay playsinline></video>
</td></tr>
</table>
</div>

<!-- jQuery (Bootstrap 的 JavaScript 插件需要引入 jQuery) -->
<script src="https://code.jquery.com/jquery.js"></script>
<!-- 包括所有已编译的插件 -->
<script src="js/bootstrap.min.js"></script>

16.最终效果

 

 

猜你喜欢

转载自blog.csdn.net/u011616934/article/details/124738847