(1)第一步
首先創(chuàng)建一個(gè)信令服務(wù)器
需要安裝
express
socket.io
npm i express socket.io
在根目錄下創(chuàng)建 index.js
信令服務(wù)器
index.js:
var express = require('express'); // web 框架
const fs = require('fs'); // 文件讀取
var app = express(); // 站點(diǎn)
app.use("/", express.static("public")); // 開(kāi)放 public 目錄
// 證書(shū)配置推正,web 調(diào)起攝像頭需要 https
let options = {
key: fs.readFileSync('./ssl/privatekey.pem'), // 證書(shū)文件的存放目錄
cert: fs.readFileSync('./ssl/certificate.pem')
}
const https = require('https').Server(options, app); // 創(chuàng)建 https 服務(wù)器
const io = require('socket.io')(https);
io.on('connection', (socket) => {
// 新連接
socket.on('conn', function (userName) {
socket.join('room'); // 加入房間
socket.emit('conn', userName);
console.log('新用戶:' + userName);
});
// 接收 Offer 信令并發(fā)送給其他連接
socket.on('signalOffer', function (message) {
socket.to('room').emit('signalOffer', message);
});
// 接收 Answer 信令
socket.on('signalAnswer', function (message) {
socket.to('room').emit('signalAnswer', message);
});
// 接收 iceOffer
socket.on('iceOffer', function (message) {
socket.to('room').emit('iceOffer', message);
});
// 接收 iceAnswer
socket.on('iceAnswer', function (message) {
socket.to('room').emit('iceAnswer', message);
});
});
const config = {
port: 8101 // 啟用端口
};
https.listen(config.port); // 啟動(dòng)服務(wù)
console.log('https listening on ' + config.port);
啟動(dòng)服務(wù)器:node index.js
(2)第二步
在 /public
目錄下創(chuàng)建 index.html 通話頁(yè)面
/public/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>音視頻通話</title>
</head>
<body>
<div class="container">
<h1>音視頻通話</h1>
<hr>
<div class="video_container" align="center">
<!-- 本地視頻流 -->
<video id="local_video" controls autoplay muted webkit-playsinline></video>
<!-- 遠(yuǎn)端視頻流 -->
<video id="remote_video" controls autoplay muted webkit-playsinline></video>
</div>
<hr>
<button id="startButton">加入房間</button>
<button id="hangupButton">掛斷</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.0/socket.io.min.js"></script>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="main.js"></script>
</div>
</body>
</html>
(3)第三步
創(chuàng)建 main.js (WebRTC關(guān)鍵代碼)
/public/main.js:
'use strict'
var localVideo = document.getElementById('local_video'); // 本地視頻 Video
var remoteVideo = document.getElementById('remote_video'); // 遠(yuǎn)端視頻 Video
var startButton = document.getElementById('startButton'); // 加入房間按鈕
var hangupButton = document.getElementById('hangupButton'); // 掛斷按鈕
var pc; // RTCPeerConnection 實(shí)例(WebRTC 連接實(shí)例)
var localStream; // 本地視頻流
var socket = io.connect(); // 創(chuàng)建 socket 連接
// ice 打洞服務(wù)器
var config = {
'iceServers': [{
'urls': 'stun:stun.l.google.com:19302'
}]
};
// offer 配置
const offerOptions = {
offerToReceiveVideo: 1,
offerToReceiveAudio: 1
};
hangupButton.disabled = true;
startButton.addEventListener('click', startAction);
hangupButton.addEventListener('click', hangupAction);
// 點(diǎn)擊加入房間
function startAction () {
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(function (mediastream) {
localStream = mediastream; // 本地視頻流
localVideo.srcObject = mediastream; // 播放本地視頻流
startButton.disabled = true;
socket.emit('conn', 'room'); // 連接 socket
}).catch(function (e) {
console.log(JSON.stringify(e));
});
}
// socket 連接成功
socket.on('conn', function (room, id) {
hangupButton.disabled = false;
pc = new RTCPeerConnection(config); // 創(chuàng)建 RTC 連接
localStream.getTracks().forEach(track => pc.addTrack(track, localStream)); // 添加本地視頻流 track
// 創(chuàng)建 Offer 請(qǐng)求
pc.createOffer(offerOptions).then(function (offer) {
pc.setLocalDescription(offer); // 設(shè)置本地 Offer 描述滋早,(設(shè)置描述之后會(huì)觸發(fā)ice事件)
socket.emit('signalOffer', offer); // 發(fā)送 Offer 請(qǐng)求信令
});
// 監(jiān)聽(tīng) ice
pc.addEventListener('icecandidate', function (event) {
var iceCandidate = event.candidate;
if (iceCandidate) {
// 發(fā)送 iceOffer 請(qǐng)求
socket.emit('iceOffer', iceCandidate);
}
});
});
// 接收 Offer 請(qǐng)求信令
socket.on('signalOffer', function (message) {
pc.setRemoteDescription(new RTCSessionDescription(message)); // 設(shè)置遠(yuǎn)端描述
// 創(chuàng)建 Answer 請(qǐng)求
pc.createAnswer().then(function (answer) {
pc.setLocalDescription(answer); // 設(shè)置本地 Answer 描述
socket.emit('signalAnswer', answer); // 發(fā)送 Answer 請(qǐng)求信令
})
// 監(jiān)聽(tīng)遠(yuǎn)端視頻流
pc.addEventListener('addstream', function (event) {
remoteVideo.srcObject = event.stream; // 播放遠(yuǎn)端視頻流
});
});
// 接收 Answer 請(qǐng)求信令
socket.on('signalAnswer', function (message) {
pc.setRemoteDescription(new RTCSessionDescription(message)); // 設(shè)置遠(yuǎn)端描述
console.log('remote answer');
// 監(jiān)聽(tīng)遠(yuǎn)端視頻流
pc.addEventListener('addstream', function (event) {
remoteVideo.srcObject = event.stream;
});
});
// 接收 iceOffer
socket.on('iceOffer', function (message) {
addIceCandidates(message)
});
// 接收 iceAnswer
socket.on('iceAnswer', function (message) {
addIceCandidates(message)
});
// 添加 IceCandidate
function addIceCandidates (message) {
if (pc !== 'undefined') {
pc.addIceCandidate(new RTCIceCandidate(message));
}
}
// 掛斷
function hangupAction () {
localStream.getTracks().forEach(track => track.stop());
pc.close();
pc = null;
hangupButton.disabled = true;
startButton.disabled = false;
}
(4)在局域網(wǎng)內(nèi)開(kāi)啟音視頻通話
在瀏覽器輸入地址 https://192.168.0.100:8101
本機(jī)可以輸入 https://localhost:8101
如遇提示 您的連接不是私密連接
請(qǐng)繼續(xù)點(diǎn)擊 高級(jí)
=> 繼續(xù)前往192.168.0.100(不安全)
1.png
點(diǎn)擊 繼續(xù)前往
2.png
通話頁(yè)面:
3.png
點(diǎn)擊 加入房間
4.png
ios手機(jī)端畫(huà)面:
在 ios safari 瀏覽器中嗽冒,視頻流不會(huì)自動(dòng)播放蒙保,需要點(diǎn)擊播放按鈕
5.png
使用兩只手指縮放到頁(yè)面上進(jìn)行播放:
6.png
android手機(jī)端畫(huà)面:
在 android 瀏覽器中柒傻,視頻流可以自動(dòng)播放盏道,請(qǐng)盡量使用高版本萤晴,以避免兼容性問(wèn)題
7.png
當(dāng)前代碼只支持1對(duì)1模式
源碼點(diǎn)擊