前言
先回顾一下上两篇的内容。第一篇,是调用摄像头采集视频的案例,首先创建了node.js 项目,然后用getUserMedia()这个东西获取MediaStream,然后调用video.srcObject = mediasteam,浏览器就显示采集内容了。第二篇,是单机单个页面呼叫的案例,主要目的是梳理信令转发流程,核心点在于RTCPeerConnection()这个迷人的对象。
本篇将引入socket.io,实现两台电脑之间的呼叫的案例。
说明:我现实情况是笔记本有摄像头,而台式机没有,因此效果是笔记本采集视频,台式机上线后,笔记本呼叫台式机,然后台式机显示笔记本端采集的视频。
好,下面进入实际案例
客户端
创建node.js项目,在项目文件下创建alice.html(呼叫者页面)文件,代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>alice</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="container">
<h1>ailce</h1>
<hr>
<div class="video_container" align="center">
<video id="local_video" poster="img/video_fill.jpg" autoplay muted></video>
</div>
<hr>
<button id="startButton">获取本地视频</button>
<button id="callButton">呼叫bob</button>
<button id="hangupButton">挂断</button>
<script src="/socket.io/socket.io.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/alice.js"></script>
</div>
</body>
</html>
创建js文件夹,在其文件夹下创建alice.js文件,打开文件键入如下代码:
'use strict'
var localVideo = document.getElementById('local_video');
var startButton = document.getElementById('startButton');
var callButton = document.getElementById('callButton');
var hangupButton = document.getElementById('hangupButton');
var pc;
var localStream;
var socket = io.connect();
var config = {
'iceServers': [{
'urls': 'stun:stun.l.google.com:19302'
}]
};
const offerOptions = {
offerToReceiveVideo: 1,
offerToReceiveAudio: 1
};
callButton.disabled = true;
hangupButton.disabled = true;
startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);
function startAction() {
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(function (mediastream) {
localStream = mediastream;
localVideo.srcObject = mediastream;
startButton.disabled = true;
}).catch(function (e) {
console.log(JSON.stringify(e));
});
}
socket.on('create', function (room, id) {
console.log('alice创建聊天房间');
console.log(room + id);
});
socket.on('call', function () {
callButton.disabled = false;
});
socket.on('signal', function (message) {
if (pc !== 'undefined') {
pc.setRemoteDescription(new RTCSessionDescription(message));
console.log('remote answer');
}
});
socket.on('ice', function (message) {
if (pc !== 'undefined') {
pc.addIceCandidate(new RTCIceCandidate(message));
console.log('become candidate');
}
});
socket.emit('create or join', 'room');
function callAction() {
callButton.disabled = true;
hangupButton.disabled = false;
pc = new RTCPeerConnection(config);
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
pc.createOffer(offerOptions).then(function (offer) {
pc.setLocalDescription(offer);
socket.emit('signal', offer);
});
pc.addEventListener('icecandidate', function (event) {
var iceCandidate = event.candidate;
if (iceCandidate) {
socket.emit('ice', iceCandidate);
}
});
}
function hangupAction() {
localStream.getTracks().forEach(track => track.stop());
pc.close();
pc = null;
hangupButton.disabled = true;
callButton.disabled = true;
startButton.disabled = false;
}
创建bob.html文件(被呼叫端页面),编写如下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>对方的视频</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div class="container">
<h1>对方的视频</h1>
<hr>
<div class="video_container" align="center">
<video id="remote_video" poster="img/video_fill.jpg" controls autoplay></video>
</div>
<hr>
<script src="/socket.io/socket.io.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/bob.js"></script>
</div>
</body>
</html>
在js文件中创建bob.js文件,编写如下代码:
'use strict'
var remoteVideo = document.getElementById('remote_video');
var socket = io.connect();
var config = {
'iceServers': [{
'urls': 'stun:stun.l.google.com:19302'
}]
};
var pc;
socket.emit('create or join', 'room');
socket.on('join', function (room, id) {
console.log('bob加入房间');
});
socket.on('signal', function (message) {
pc = new RTCPeerConnection(config);
pc.setRemoteDescription(new RTCSessionDescription(message));
pc.createAnswer().then(function (answer) {
pc.setLocalDescription(answer);
socket.emit('signal', answer);
});
pc.addEventListener('icecandidate', function (event) {
var iceCandidate = event.candidate;
if (iceCandidate) {
socket.emit('ice', iceCandidate);
}
});
pc.addEventListener('addstream', function (event) {
remoteVideo.srcObject = event.stream;
});
});
socket.on('ice', function (message) {
pc.addIceCandidate(new RTCIceCandidate(message));
});
至此,呼叫端和被呼叫端代码编写完成。
信令转发服务器
引入socket.io模块(sockio.io是一个开源的及时通讯框架)
npm install socket.io
新建index.js文件,编写如下代码:
'use strict'
var express = require('express');
var app = express();
var http = require('http').createServer(app);
var io = require('socket.io')(http);
app.use('/css',express.static('css'));
app.use('/js',express.static('js'));
app.use('/img',express.static('img'));
app.get('/',function(request,response){
response.sendFile(__dirname +'/index.html');
});
app.get('/alice',function(request,response){
response.sendFile(__dirname+"/alice.html")
});
app.get('/bob',function(request,response){
response.sendFile(__dirname+"/bob.html")
});
io.on('connection',function(socket){
console.log('有用户加入进来');
socket.on('signal',function(message){
socket.to('room').emit('signal',message);
});
socket.on('ice',function(message){
socket.to('room').emit('ice',message);
});
socket.on('create or join',function(room){
var clientsInRoom = io.sockets.adapter.rooms[room];
var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;
console.log(numClients);
if(numClients===0){
socket.join(room);
socket.emit('create', room, socket.id);
console.log('caller joined');
}else if(numClients===1){
socket.join(room);
socket.to('room').emit('call');
console.log('callee joined');
}
});
});
var server = http.listen(8080,function(){
var host = server.address().address;
var port = server.address().port;
console.log('listening on:http://s%:s%',host,port);
});
至此,案例代码编写完成
测试结果
命令行 启动项目,打开chrome,地址栏输入localhost:8080/alice,打开alice页面(呼叫者),点击获取本地视频按钮,显示内容如下:
alice.png打开bob(被呼叫者)页面,显示内容如下:
bob.png被呼叫者上线后,呼叫者页面呼叫按钮可用,
call.png
点击呼叫bob,显示效果如下:
总结
本章完成了局域网不同设备呼叫的案例。引入了socket.io框架,为信令转发提供服务,对信令转发的认识更加清晰了。在建立RTCPeerConnection()对象的时候,配置了iceServer,学名叫stun服务,这是一个协助p2p连接的服务。搭建stun服务,开源项目有coturn。
下面将在局域网打通android手机端和笔记本之间的连接。
网友评论