利用WebRTC实现的一个局域网内单向视频的示例。由于是在局域网内,所用没有使用到STUN/TURN 服务器,如果需要则可以使用coturn开源库来搭建。然后使用Node.js搭建了一个简单的信令服务器。运行时,需要先打开接收端页面,再打开发送端页面,才能正常看到视频流。
下面是主要部分的代码:
服务器端
server.js:
'use strict'
var http = require('http');
var https = require('https');
var fs = require('fs');
var serveIndex = require('serve-index');
var express = require('express');
//socket.io
var socketIo = require('socket.io');
var app = express();
//顺序不能换
app.use(serveIndex('./public'));
app.use(express.static('./public'));
var options = {
key: fs.readFileSync('./cert/oxbbnb.top_nginx/oxbbnb.top.key'),
cert: fs.readFileSync('./cert/oxbbnb.top_nginx/oxbbnb.top_bundle.pem')
}
var https_server = https.createServer(options, app);
//bind socket.io with https_server
var io = socketIo(https_server);
// 转发双方的SDP和ICE
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('send_sdp', (msg) => {
console.log('send_sdp:\n' + msg.slice(0, 50) + '...\n');
io.emit('send_sdp', msg);
});
socket.on('recv_sdp', (msg) => {
console.log('recv_sdp:\n' + msg.slice(0, 50) + '...\n');
io.emit('recv_sdp', msg);
});
socket.on('send_ice', (msg) => {
console.log('send_ice:\n' + msg + '\n');
io.emit('send_ice', msg);
});
socket.on('recv_ice', (msg) => {
console.log('recv_ice:\n' + msg + '\n');
io.emit('recv_ice', msg);
});
socket.on('disconnect', () => {
console.log('user disconnected');
io.emit('user_disconnected');
});
});
https_server.listen(443, '0.0.0.0');
var http_server = http.createServer(app);
http_server.listen(8080, '0.0.0.0');
发送端:
sender.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC Sender</title>
</head>
<body>
<h1>WebRTC Sender</h1>
<video id="localVideo" autoplay playsinline></video>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="https://cdn.socket.io/socket.io-3.0.1.js"></script>
<script src="./js/sender.js"></script>
</body>
</html>
sender.js:
const socket = io();
var pc;
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(function (localStream) {
//document.getElementById('localVideo').srcObject = localStream;
/*
const configuration = {
iceServers: [
// STUN 服务器
{
urls: 'stun:xxx.xxx.xxx.xxx:3478',
},
// TURN 服务器
{
urls: 'turn:xxx.xxx.xxx.xxx:3478',
username: 'xxx',
credential: 'xxx'
}
]
};
pc = new RTCPeerConnection(configuration);
*/
pc = new RTCPeerConnection();
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
var offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
}
pc.createOffer(offerOptions)
.then(function (offer) {
return pc.setLocalDescription(offer);
})
.then(function () {
// 发送SDP
socket.emit('send_sdp', pc.localDescription.sdp);
//console.log('send_sdp:\n' + pc.localDescription.sdp);
});
pc.onicecandidate = event => {
if (event.candidate) {
// 发送ICE
var data = {
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
}
socket.emit('send_ice', JSON.stringify(data));
//console.log('send_ice:\n' + JSON.stringify(data));
}
};
})
.catch(function (error) {
console.error("Error accessing camera:", error);
});
//接收SDP
socket.on('recv_sdp', (sdp) => {
//console.log('receive sdp:\n' + sdp);
pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: sdp }));
})
//接收ICE
socket.on('recv_ice', (ice) => {
console.log('receive ice:\n' + ice);
var obj = JSON.parse(ice);
var iceCandidate = new RTCIceCandidate({
candidate: obj.candidate, sdpMLineIndex: obj.label, sdpMid: obj.id
});
pc.addIceCandidate(iceCandidate);
})
接收端
receiver.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC Receiver</title>
</head>
<body>
<h1>WebRTC Receiver</h1>
<video id="remoteVideo" autoplay playsinline></video>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="https://cdn.socket.io/socket.io-3.0.1.js"></script>
<script src="./js/receiver.js"></script>
</body>
</html>
receiver.js:
const socket = io();
var pc;
socket.on('send_sdp', (sdp) => {
//console.log('send_sdp sdp:\n' + sdp);
/*
const configuration = {
iceServers: [
// STUN 服务器
{
urls: 'stun:xxx.xxx.xxx.xxx:3478',
},
// TURN 服务器
{
urls: 'turn:xxx.xxx.xxx.xxx:3478',
username: 'xxx',
credential: 'xxx'
}
]
};
pc = new RTCPeerConnection(configuration);
*/
pc = new RTCPeerConnection();
pc.setRemoteDescription(new RTCSessionDescription({ type: 'offer', sdp: sdp }));
pc.createAnswer().then(function (answer) {
pc.setLocalDescription(answer);
socket.emit('recv_sdp', answer.sdp);
})
pc.ontrack = (event) => {
document.getElementById('remoteVideo').srcObject = event.streams[0];
}
})
socket.on('send_ice', (ice) => {
console.log('send_ice ice:\n' + ice);
var obj = JSON.parse(ice);
var iceCandidate = new RTCIceCandidate({
candidate: obj.candidate, sdpMLineIndex: obj.label, sdpMid: obj.id
});
pc.addIceCandidate(iceCandidate);
pc.onicecandidate = (e) => {
if (e.candidate) {
var data = {
type: 'candidate',
label: e.candidate.sdpMLineIndex,
id: e.candidate.sdpMid,
candidate: e.candidate.candidate
}
socket.emit('recv_ice', JSON.stringify(data));
//console.log('recv_ice:\n' + JSON.stringify(data));
}
}
})
socket.on('user_disconnected', () => {
console.log('user_disconnected');
if (pc) {
pc.close();
pc = null;
}
// 清除视频流
document.getElementById('remoteVideo').srcObject = null;
});