WebRTC 是一项基于 Web 技术的实时通信标准,它允许浏览器之间进行点对点的音视频通信。但光有 WebRTC 还无法完成完整的视频通话应用,我们还需要一种能够实现客户端之间数据传输的通信协议,这时候 Socket.io 就派上用场了。本文将介绍如何使用 Socket.io 结合 WebRTC 实现视频通话。
1. 创建基本网页结构
首先,我们需要创建一个 HTML 文件,用于实现用户界面。在这个 HTML 文件中,我们首先引入 Socket.io,然后再引入 WebRTC 所需的库。接下来,我们创建视频容器,通过 JavaScript 构建 UI,添加按钮以便连接到服务器。
下面是示例代码:
// www.javascriptcn.com code example
<!DOCTYPE html>
<html>
<head>
<title>Socket.io实现基于WebRTC的视频通话</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="js/pubnub.min.js"></script>
<script src="js/webrtc.io.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div id="app">
<video
id="localVideo"
height="480"
width="640"
autoplay
muted
></video>
<video id="remoteVideo" height="480" width="640" autoplay></video>
<br />
<button v-if="!connected" @click="connect">连接服务器</button>
<button v-if="connected" @click="disconnect">关闭连接</button>
</div>
</body>
<script src="js/script.js"></script>
</html>2. 建立 WebSocket 连接
在本次视频通话中,我们需要使用 WebSocket 将客户端间的信令传输。WebRTC 中使用的信令包括 SDP 和 ICE Candidates,SDP 描述客户端的媒体信息,ICE Candidates 描述客户端的网络地址信息。
我们可以通过 Socket.io 客户端来建立WebSocket 连接,具体方式为在脚本文件中使用io()方法:
var socket = io('http://localhost:3000');当连接成功时,我们可以接收connect事件。
3. 创建用户界面和控制逻辑
接下来,我们需要编写客户端代码,用于连接到服务器、建立流、发出信令,以及处理来自其他客户端的信令。我们使用 Vue.js 管理用户界面,并创建localStream和remoteStream两个变量,用于存储本机流和远程流。
下面是相关代码:
// www.javascriptcn.com code example
var app = new Vue({
el: '#app',
data: {
socket: socket,
connected: false,
},
methods: {
connect: function () {
var context = this;
this.socket.on('connect', function () {
console.log('Connected with Server');
context.connected = true;
context.getLocalStream();
});
},
disconnect: function () {
var context = this;
this.socket.emit('close');
context.connected = false;
},
getLocalStream: function () {
var context = this;
navigator.mediaDevices
.getUserMedia({
video: true,
audio: true,
})
.then(function (stream) {
document.getElementById('localVideo').srcObject = stream;
context.localStream = stream;
context.socket.emit('create or join', room);
context.socket.on('created', function (room) {
console.log('created room ' + room);
context.isInitiator = true;
});
context.socket.on('joined', function (room) {
console.log('joined room ' + room);
context.isChannelReady = true;
});
context.socket.on('ready', function () {
context.isChannelReady = true;
context.createPeerConnection(context.isInitiator);
});
context.socket.on('offer', function (message) {
if (!context.isInitiator && !context.isStarted) {
context.createPeerConnection(false);
context.isStarted = true;
}
context.peerConnection.setRemoteDescription(new RTCSessionDescription(message));
context.doAnswer();
});
context.socket.on('answer', function (message) {
if (context.isStarted) {
context.peerConnection.setRemoteDescription(new RTCSessionDescription(message));
}
});
context.socket.on('candidate', function (message) {
var candidate = new RTCIceCandidate({
sdpMLineIndex: message.label,
candidate: message.candidate,
});
context.peerConnection.addIceCandidate(candidate);
});
})
.catch(function (e) {
console.log('getUserMedia() error: ' + e.name);
});
},
createPeerConnection: function (isInitiator) {
try {
this.peerConnection = new RTCPeerConnection(this.servers);
this.peerConnection.onicecandidate = this.handleIceCandidate;
this.peerConnection.onaddstream = this.handleRemoteStreamAdded;
this.peerConnection.onremovestream = this.handleRemoteStreamRemoved;
this.peerConnection.addStream(this.localStream);
if (isInitiator) {
this.peerConnection.createOffer(this.setLocalAndSendMessage, this.handleError);
}
} catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
return;
}
this.isStarted = true;
},
handleIceCandidate: function (event) {
if (event.candidate) {
this.socket.emit('candidate', JSON.stringify({ label: event.candidate.sdpMLineIndex, candidate: event.candidate.candidate }));
}
},
handleRemoteStreamAdded: function (event) {
console.log('Remote stream added.');
this.remoteStream = event.stream;
document.getElementById('remoteVideo').srcObject = event.stream;
},
handleRemoteStreamRemoved: function (event) {
console.log('Remote stream removed. Event: ', event);
},
setLocalAndSendMessage: function (sessionDescription) {
this.peerConnection.setLocalDescription(sessionDescription);
this.socket.emit('offer', sessionDescription);
},
doAnswer: function () {
console.log('Sending answer to peer.');
this.peerConnection.createAnswer(this.setLocalAndSendMessage, this.handleError);
},
handleError: function (error) {
console.log('Error: ', error);
},
},
});4. 创建 Node.js 服务器
接下来,我们需要创建基于 Node.js 的服务器,使用 Socket.io 库进行 WebSocket 通信。我们需要在服务器端创建两个空间,用于媒体流和信令的传输。媒体流传输的空间用于处理媒体流,而信令传输的空间用于处理 SDP 和 ICE Candidates。
下面是相关代码:
// www.javascriptcn.com code example
var http = require('http');
var path = require('path');
var server = http.createServer(function (req, res) {
// Serve static files
});
var io = require('socket.io')(server);
var numClients = 0;
io.on('connection', function (socket) {
console.log(`Socket connected: ${socket.id}`);
numClients++;
socket.on('create or join', function (room) {
console.log(`create or join to room ${room}`);
if (io.nsps['/'].adapter.rooms[room] && io.nsps['/'].adapter.rooms[room].length === 1) {
socket.join(room);
socket.emit('created', room);
} else if (io.nsps['/'].adapter.rooms[room] && io.nsps['/'].adapter.rooms[room].length === 2) {
socket.join(room);
socket.emit('joined', room);
io.to(room).emit('ready');
} else {
socket.leave(room);
socket.emit('full', room);
}
});
socket.on('close', function () {
numClients--;
console.log(`Socket disconnected: ${socket.id}`);
});
socket.on('offer', function (message) {
console.log(`<offer> from ${socket.id}: ${message}`)
socket.to(message.room).emit('offer', message);
});
socket.on('answer', function (message) {
console.log(`<answer> from ${socket.id}: ${message}`);
socket.to(message.room).emit('answer', message);
});
socket.on('candidate', function (message) {
console.log(`<candidate> from ${socket.id}: ${message}`);
socket.to(message.room).emit('candidate', message);
});
});5. 运行程序
现在,打开浏览器并输入所需参数,我们能够连接到服务器并完成视频通话。通过此实现方式,我们可以使用 WebRTC 实现音视频信息传输,这好比自己打造一个私有的 skpe。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67cda633e46428fe9e7485eb