node app.js // 默认端口https://localhost:3000
全局安装nodemon 用 nodemon app.js // 热更新启动
node启动的是https 可以掉起本地获取摄像头权限
浏览器输入https://localhost:3000
https://localhost:3000/ 获取音视频设备信息
https://localhost:3000/room 完整项目 展示本地流 连接远程流
https://localhost:3000/mediastream
WebRTC 主要提供了三个核心的 API:
getUserMedia:可以获取本地的媒体流,一个流包含几个轨道,比如视频和音频轨道。
RTCPeerConnection:用于建立 P2P 连接以及传输多媒体数据。
RTCDataChannel:建立一个双向通信的数据通道,可以传递多种数据类型。
如果假设,对等体 A 想要与对等体 B 建立 WebRTC 连接,则需要执行以下操作:
Peer A使用 ICE 生成它的 ICE候选者。在大多数情况下,它需要 NAT(STUN)的会话遍历实用程序或 NAT(TURN)服务器的遍历使用中继。
Peer A 将 ICE候选者 和会话描述捆绑到一个对象中。该对象在对等体 A 内存储为本地描述(对等体自己的连接信息),并通过信令机制传送给对等体 B.这部分称为要约。
对等体 B 接收该提议并将其存储为远程描述(另一端的对等体的连接信息)以供进一步使用。对等体 B 生成它自己的 ICE候选者和会话描述,将它们存储为本地描述,并通过信令机制将其发送给对等体A.这部分称为答案。 (注:如前所述,步骤2和3中的ICE候选人也可以单独发送)
对等体 A 从对等体 B 接收答案并将其存储为远程描述。
这样,两个对等体都具有彼此的连接信息,并且可以通过 WebRTC 成功开始通信!
function start() {
if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log("不支持");
return
} else {
var deviceId = viodeSource.value;
var constraints = {
video: {
width: 640,
height: 480,
frameRate: 15,
deviceId: deviceId ? deviceId : undefined
audio: {
noiseSuppression: true,
echoCancellation: true
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.then(gotDevices)
.catch(handleError)
function gotMediaStream(stream) {
console.log(stream)
videoplay.srcObject = stream;
window.stream = stream;
var videoTrack = stream.getVideoTracks()[0];
var videoConstraints = videoTrack.getSettings();
constraints.textContent = JSON.stringify(videoConstraints);
return navigator.mediaDevices.enumerateDevices();
建立信令服务器
使用node+koa2搭建信令服务器
引入socket.io
npm i socket.io --save
const Koa = require('koa');
const app = new Koa();
const staticFiles = require('koa-static');
const path = require("path");
const https = require("https");
const fs = require("fs");
const socketIo = require('socket.io');
const log4js = require("log4js");
let logger = log4js.getLogger();
logger.level = "debug";
app.use(staticFiles(path.resolve(__dirname, "public")));
const options = {
key: fs.readFileSync("./server.key", "utf8"),
cert: fs.readFileSync("./server.cert", "utf8")
let server = https.createServer(options, app.callback())
server.listen(3000, () => {
console.log(`开始了localhost:${3000}`)
const io = socketIo(server);
io.on('connection', (socket) => {
socket.on("join", (room) => {
socket.join(room);
var myRoom = io.sockets.adapter.rooms.get(room);
var users = myRoom ? myRoom.size : 0;
logger.debug('--房间用户数量--',users, 'room',room);
if(users < 9) {
socket.emit("joined", room, socket.id);
if(users>1) {
socket.to(room).emit("otherjoin", room, socket.id);
} else {
socket.leave(room);
socket.emit("full", room, socket.id);
socket.on("leave", (room) => {
var myRoom = io.sockets.adapter.rooms.get(room);
console.log("myRoom",myRoom);
var users = myRoom ? myRoom.size : 0;
logger.debug('--房间用户数量离开房间--',users-1);
socket.to(room).emit('bye', room, socket.id);
socket.emit("leaved", room, socket.id);
socket.on("message", (room, data)=>{
console.log("message",room)
socket.to(room).emit("message",room, data)
引入<script src="../js/socket.io.min.js"></script>
function coon() {
console.log("coon")
socket = io.connect();
socket.on("joined", (roomid, id) => {
console.log("收到---joined", roomid, id);
state = "joined";
createPeerConnecion();
btnConn.disabled = true;
btnLeave.disabled = false;
console.log("recevie--joined-state--", state);
socket.on("otherjoin", (roomid, id) => {
console.log("-otherjoin-", roomid, id);
if (state === "joined_unbind") {
createPeerConnecion();
state = "joined_conn";
call();
console.log("recevie--otherjoin-state--", state);
socket.on("full", (roomid, id) => {
console.log("-full-", roomid, id);
state = "leaved";
console.log("recevie--full-state--", state);
socket.disconnect();
alert("房间已满");
btnConn.disabled = false;
btnLeave.disabled = true;
socket.on("leaved", (roomid, id) => {
console.log("-leaved-", roomid, id);
state = "leaved";
socket.disconnect();
btnConn.disabled = false;
btnLeave.disabled = true;
console.log("recevie---leaved-state--", state);
socket.on("bye", (roomid, id) => {
console.log("-bye-", roomid, id);
state = "joined_unbind";
console.log("bye-state--", state);
closePeerConnection();
socket.on("message", (roomid, data) => {
console.log("收到了客户端的 message", roomid, data);
if(data) {
if(data.type ==='offer') {
pc.setRemoteDescription(new RTCSessionDescription(data));
pc.createAnswer()
.then(getAnswer)
.catch(err=>{console.log("err 创建失败getanswer")});
} else if(data.type ==='answer') {
pc.setRemoteDescription(new RTCSessionDescription(data));
} else if(data.type ==='candidate') {
var candidate = new RTCIceCandidate({
sdpMLineIndex: data.label,
candidate: data.candidate
pc.addIceCandidate(candidate)
.then(()=>{
console.log('Successed to add ice candidate');
.catch(err=>{
console.error(err);
} else {
console.error("the message is invalid!", data);
socket.emit("join", "111111");
return;
创建RTCPeerConnection
WebRTC 中,我们通过 RTCPeerConnection建立通信双方的点对点连接,该接口提供了创建,保持,监控,关闭连接的方法的实现。
为了建立连接,我们需要一台信令服务器,用于浏览器之间建立通信时交换各种元数据(信令)。同时,还需要 STUN 或者 TURN 服务器来完成 NAT 穿透。连接的建立主要包含两个部分:信令交换和设置 ICE 候选。
RTCPeerConnection
常见开源的turn|stun服务器
创建peer
let iceServer = {
"iceServers": [
sdpSemantics: 'plan-b',
let PeerConnection = (window.PeerConnection ||
window.webkitPeerConnection00 ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection)
var peer = new RTCPeerConnection(iceServer);
谷歌调试 : chrome://webrtc-internals
回音消除和降噪
noiseSuppression
在Firefox中运行堪称完美,当关掉这个约束时,我能非常清晰的听到麦克风收
参考资料:
xirsys.com/developers/
www.yuque.com/wangdd/open…
常用的视频会议和直播架构
www.cnblogs.com/yjmyzz/p/we…
vue 使用 socket www.imooc.com/article/289…