webRTC-简易视频通讯服务器 目录
[TOC]
前言
- 本文基于
webSocket
建立了一个网络视频通讯
推荐阅读
- 《Learning WebRTC》
涵盖内容
- 从客户端获取到服务器的连接
- 识别各个连接端的用户
- 两个远程用户发起通话
- 结束通话
展示效果
展示效果
连接
- 包含两个页面:输入用户名,呼叫其他用户
创建页面
index.html
<!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>Document</title>
</head>
<style>
body{
background-color: #3D6DF2;
margin-top: 15px;
font-family: sans-serif;
color: white;
}
video {
background: black;
border: 1px solid gray;
}
.page {
position: relative;
display: block;
margin: 0 auto;
width: 500px;
height: 500px;
}
#yours{
width: 150px;
height: 150px;
position: absolute;
top: 15px;
right: 15px;
}
#theirs {
width: 500px;
height: 500px;
}
</style>
<body>
<div id="login-page" class="page">
<h2>Login As</h2>
<input type="text" id="username">
<button id="login">Login</button>
</div>
<div id="call-page" class="page">
<video id="yours" autoplay></video>
<video id="theirs" autoplay></video>
<input type="text" id="their-username">
<button id="call">Call</button>
<button id="hang-up">Hang Up</button>
</div>
</body>
<script src="client.js"></script>
</html>
获取一个连接
- 与信令服务器创建连接
let name,
connectedUser;
let connection = new WebSocket('ws://localhost:8888');
connection.onopen = function() {
console.log("Connected");
};
connection.onmessage = function(message){
console.log("Got message", message.data);
let data = JSON.parse(message.data);
switch(data.type){
case "login":
onLogin(data.success);
break;
case "offer":
onOffer(data.offer, data.name);
break;
case "answer":
onAnswer(data.answer);
break;
case "candidate":
onCandidate(data.candidate);
break;
case "leave":
onLeave();
break;
default:
break;
}
};
connection.onerror = function(err) {
console.log(err);
}
function send(message) {
if(connectedUser) {
message.name = connectedUser;
}
connection.send(JSON.stringify(message));
}
相关学习资料推荐,点击下方链接免费报名,先码住不迷路~】
音视频免费学习地址:https://xxetb.xet.tech/s/2cGd0
登录
let loginPage = document.querySelector('#login-page'),
usernameInput = document.querySelector('#username'),
loginButton = document.querySelector('#login'),
callPage = document.querySelector('#call-page'),
theirUsernameInput = document.querySelector('#theirusername'),
callButton = document.querySelector('#call'),
hangUpButton = document.querySelector('#hang-up');
callPage.style.display = 'none';
loginButton.addEventListener('click', event=>{
name = usernameInput.value;
if(name.length > 0){
send({
type: "login",
name: name
});
}
});
function onLogin(success) {
if(success === false){
alert("Login unsuccessfully, please try a different name.");
}else {
loginPage.style.display = 'none';
callPage.style.display = 'block';
}
// 准备好通话的通道
startConnection();
}
开始对等连接
- 从相机中获取视频流
- 验证用户的浏览器是否支持
WebRTC
- 创建
RTCPeerConnection
对象
let yourVideo = document.querySelector('#yours'),
theirVideo = document.querySelector('#theirs'),
yourConnection,
connectedUser,
stream;
function startConnection() {
if(hasUserMedia()){
navigator.getUserMedia({
video: true,
audio: true
},
myStream => {
stream = myStream;
try{
yourVideo.src = window.URL.createObjectURL(stream);
}catch(e){
yourVideo.srcObject = stream;
}
if(hasRTCPeerConnection()){
setupPeerConnection(stream);
}else{
alert("不支持webRTC");
}
},
error => {
console.log(error);
}
);
}else{
alert("不支持webRTC");
}
}
function setupPeerConnection(stream) {
let configuration = {
"iceServers":[
{"url":"stun:localhost:8888"}
]
};
yourConnection = new RTCPeerConnection(configuration);
// 设置流量监听
yourConnection.addStream(stream);
yourConnection.onaddstream = function(e) {
try{
theirVideo.src = window.URL.createObjectURL(e.stream);
}catch(e){
theirVideo.srcObject = stream;
}
};
yourConnection.onicecandidate = function(event) {
if(event.candidate){
send({
type: "candidate",
candidate: event.candidate
});
}
};
}
function hasUserMedia() {
navigator.getUserMedia = navigator.getUserMedia
|| navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia
|| navigator.msGetUserMedia;
return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
window.RTCPeerConnection = window.RTCPeerConnection
|| window.webkitRTCPeerConnection
|| window.mozRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription
|| window.webkitRTCSessionDescription
|| window.mozRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate
|| window.webkitRTCIceCandidate
|| window.mozeRTCIceCandidate;
return !!window.RTCPeerConnection;
}
发起通话
setLocalDescription()
:更改与连接关联的本地描述。此说明指定连接的本地端的属性,包括媒体格式。setRemoteDescription()
:将指定的会话描述设置为远程对等方的当前提供或应答。描述指定连接远端的属性,包括媒体格式。addIceCandidate()
:通过信令信道从远程对等方接收新的ICE候选,它通过调用将新接收的候选发送到浏览器的ICE代理
callButton.addEventListener('click', function(){
let theirUsername = theirUsernameInput.value;
if(theirUsername.length > 0){
startPeerConnection(theirUsername);
}
});
function startPeerConnection(user) {
connectedUser = user;
// offer
yourConnection.createOffer(offer=>{
send({
type: "offer",
offer: offer
});
yourConnection.setLocalDescription(offer);
},
err => {
alert("An error has occurred");
});
}
function onOffer(offer, name) {
connectedUser = name;
yourConnection.setRemoteDescription(new RTCSessionDescription(offer));
yourConnection.createAnswer(function(answer){
yourConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
},
err =>{
alert("An error");
}
);
}
function onAnswer(answer){
yourConnection.setRemoteDescription(new RTCSessionDescription(answer));
}
function onCandidate(candidate){
yourConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
检测通信
- 调试实时应用困难在:许多事件发生在同一时刻,要完整描述某一时刻发生了什么很难
Chrome
中View
->Developer
->Developer Tools
可以看到webSocket
得通信状态
挂断电话
- 通知其他用户关闭通话
- 销毁本地连接,允许进行新的通话
过程
- 通知服务器,断开连接
- 通知
RTCPeerConnection
关闭,停止发送数据流给其他用户 - 再次设置,连接实例设置为打开状态,以接受新的通话
hangUpButton.addEventListener("click", ()=>{
send({
type: "leave"
});
onLeave();
});
function onLeave() {
connectedUser = null;
theirVideo.srcObject = null;
yourConnection.close();
yourConnection.onicecandidate = null;
yourConnection.onaddstream = null;
setupPeerConnection(stream);
}
webRTC
全部代码总结
- 全部代码整合
// 变量声明
let name,
connectedUser;
let connection = new WebSocket('ws://localhost:8888');
let yourVideo = document.querySelector('#yours'),
theirVideo = document.querySelector('#theirs'),
yourConnection,
stream;
let loginPage = document.querySelector('#login-page'),
usernameInput = document.querySelector('#username'),
loginButton = document.querySelector('#login'),
callPage = document.querySelector('#call-page'),
theirUsernameInput = document.querySelector('#their-username'),
callButton = document.querySelector('#call'),
hangUpButton = document.querySelector('#hang-up');
callPage.style.display = 'none';
// 点击按钮登录
loginButton.addEventListener('click', event=>{
name = usernameInput.value;
if(name.length > 0){
send({
type: "login",
name: name
});
}
});
// websocket 连接
connection.onopen = function() {
console.log("Connected");
};
// 监听websocket信息
connection.onmessage = function(message){
console.log("Got message", message.data);
let data = JSON.parse(message.data);
switch(data.type){
case "login":
onLogin(data.success);
break;
case "offer":
onOffer(data.offer, data.name);
break;
case "answer":
onAnswer(data.answer);
break;
case "candidate":
onCandidate(data.candidate);
break;
case "leave":
onLeave();
break;
default:
break;
}
};
// websocket报错信息
connection.onerror = function(err) {
console.log(err);
}
// Alia 以JSON格式发送信息
function send(message) {
if(connectedUser) {
message.name = connectedUser;
}
connection.send(JSON.stringify(message));
}
function onLogin(success) {
if(success === false){
alert("Login unsuccessfully, please try a different name.");
}else {
loginPage.style.display = 'none';
callPage.style.display = 'block';
}
// 准备好通话的通道
startConnection();
}
// call呼叫
callButton.addEventListener('click', function(){
let theirUsername = theirUsernameInput.value;
if(theirUsername.length > 0){
startPeerConnection(theirUsername);
}
});
// 挂断
hangUpButton.addEventListener("click", ()=>{
send({
type: "leave"
});
onLeave();
});
function onOffer(offer, name) {
connectedUser = name;
yourConnection.setRemoteDescription(new RTCSessionDescription(offer));
yourConnection.createAnswer(function(answer){
yourConnection.setLocalDescription(answer);
send({
type: "answer",
answer: answer
});
},
err =>{
alert("An error");
}
);
}
function onAnswer(answer){
yourConnection.setRemoteDescription(new RTCSessionDescription(answer));
}
function onCandidate(candidate){
yourConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
function onLeave() {
connectedUser = null;
theirVideo.srcObject = null;
yourConnection.close();
yourConnection.onicecandidate = null;
yourConnection.onaddstream = null;
setupPeerConnection(stream);
}
// 函数的polyfill
function hasUserMedia() {
navigator.getUserMedia = navigator.getUserMedia
|| navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia
|| navigator.msGetUserMedia;
return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
window.RTCPeerConnection = window.RTCPeerConnection
|| window.webkitRTCPeerConnection
|| window.mozRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription
|| window.webkitRTCSessionDescription
|| window.mozRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate
|| window.webkitRTCIceCandidate
|| window.mozeRTCIceCandidate;
return !!window.RTCPeerConnection;
}
// 开始连接
function startConnection() {
if(hasUserMedia()){
navigator.getUserMedia({
video: true,
audio: false
},
myStream => {
stream = myStream;
try{
yourVideo.src = window.URL.createObjectURL(stream);
}catch(e){
yourVideo.srcObject = stream;
}
if(hasRTCPeerConnection()){
setupPeerConnection(stream);
}else{
alert("不支持webRTC");
}
},
error => {
console.log(error);
}
);
}else{
alert("不支持webRTC");
}
}
//
function setupPeerConnection(stream) {
let configuration = {
"iceServers":[
{"url":"stun:localhost:8888"}
]
};
yourConnection = new RTCPeerConnection(configuration);
// 设置流量监听
yourConnection.addStream(stream);
yourConnection.onaddstream = function(e) {
try{
theirVideo.src = window.URL.createObjectURL(e.stream);
}catch(e){
theirVideo.srcObject = stream;
}
};
// 设置ICE处理事件
yourConnection.onicecandidate = function(event) {
if(event.candidate){
send({
type: "candidate",
candidate: event.candidate
});
}
};
}
// 开始创建offer
function startPeerConnection(user) {
connectedUser = user;
// offer
yourConnection.createOffer(offer=>{
send({
type: "offer",
offer: offer
});
yourConnection.setLocalDescription(offer);
},
err => {
alert("An error has occurred");
});
}
完整的信令服务器
const WebSocketServer = require('ws').Server,
wss = new WebSocketServer({port: 8888}),
users = {};
wss.on("connection", connection => {
console.log("User connected");
connection.on("message", message => {
// console.log("Got message:", message);
let data, conn;
try{
data = JSON.parse(message);
}catch(e) {
console.log(e);
data = {};
}
switch(data.type) {
case "login":
console.log("User logged in as", data.name);
if(users[data.name]) {
sendTo(connection, {
type: "login",
success: false
});
}else {
users[data.name] = connection;
connection.name = data.name;
sendTo(connection, {
type: "login",
success: true
});
}
break;
case "offer":
console.log("sending offer to:", data.name);
conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "offer",
offer: data.offer,
name: connection.name
});
}
break;
case "answer":
console.log("sending answer to:", data.name);
conn = users[data.name];
if(conn != null){
connection.otherName = data.name;
sendTo(conn, {
type: "answer",
answer: data.answer
})
}
break;
case "candidate":
console.log("sending to", data.name);
conn = users[data.name];
if(conn != null){
sendTo(conn, {
type: "candidate",
candidate: data.candidate
});
}
break;
case "leave":
console.log("Disconnected user from ", data.name);
conn = users[data.name];
conn.otherName = null;
if(conn != null){
sendTo(conn, {
type: "leave"
});
}
break;
default:
sendTo(connection, {
type: "error",
message: "Unrecognized command: " + data.type
});
break;
}
});
});
wss.on("close", function(){
if(connection.name){
delete users[connection.name];
if(connection.otherName) {
console.log("Disconnected,",connection.otherName);
let conn = users[connection.otherName];
conn.otherName = null;
if(conn != null){
sendTo(conn,{
type: "leave"
});
}
}
}
});
wss.on("listening", () => {
console.log("Server started...");
});
function sendTo(conn, message) {
conn.send(JSON.stringify(message));
}
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓