什么是webRTC?
WebRTC(Web Real-Time Communication)是一种用于在Web浏览器之间实时传输音频、视频和数据的开放标准和技术集合。 WebRTC 提供了一组 API 和协议,使得开发者可以直接在Web浏览器中实现点对点的实时通信,而无需使用第三方插件或应用程序。它通过使用浏览器内置的音频、视频和数据通道,实现了实时的音视频传输和数据传输。 主要特点和功能:
- 实时音视频通信:WebRTC 可以在不同的浏览器之间直接传输音频和视频流,实现实时的语音通话和视频通话。
- 数据传输:除了音视频流,WebRTC 还提供了可靠的数据传输通道(DataChannel),可以用于传输任意类型的数据。
- 点对点通信:WebRTC 的设计目标之一是支持点对点的通信,即直接在浏览器之间建立连接,不需要经过服务器的转发。
- 安全性:WebRTC 内置了强大的安全性机制,包括加密和身份验证,以确保通信过程的机密性和完整性。
- 跨平台支持:WebRTC 支持在不同的平台和设备上进行实时通信,包括桌面浏览器、移动浏览器和移动应用程序。
WebRTC 在实时通信、视频会议、在线教育、远程协作、在线游戏和物联网等领域有着广泛的应用。它为开发者提供了强大的工具和标准,使得构建实时通信应用变得更加简便和可靠。
音视频采集
WebRTC使用getUserMedia获取摄像头与话筒对应的媒体流对象MediaStream,媒体流可以通过WebRTC进行传输,并在多个对等端之间共享。将流对象赋值给视频元素的srcObject,实现本地播放音视频
配置项 |
描述 |
audio |
指定是否捕获音频流。值为 true 表示捕获音频流,值为 false 表示不捕获音频流。 |
video |
指定是否捕获视频流。值为 true 表示捕获视频流,值为 false 表示不捕获视频流。 |
audio.deviceId |
指定要使用的音频输入设备的唯一标识符。 |
audio.groupId |
指定音频输入设备所属的设备组的唯一标识符。 |
audio.echoCancellation |
指定是否启用回声消除。值为 true 表示启用回声消除,值为 false 表示禁用回声消除。 |
audio.autoGainControl |
指定是否启用自动增益控制。值为 true 表示启用自动增益控制,值为 false 表示禁用自动增益控制。 |
audio.noiseSuppression |
指定是否启用降噪。值为 true 表示启用降噪,值为 false 表示禁用降噪。 |
audio.latency |
指定音频流的延迟时间(以毫秒为单位)。 |
video.deviceId |
指定要使用的视频输入设备的唯一标识符。 |
video.groupId |
指定视频输入设备所属的设备组的唯一标识符。 |
video.width |
指定视频流的宽度(以像素为单位)。 |
video.height |
指定视频流的高度(以像素为单位)。 |
video.frameRate |
指定视频流的帧率(每秒帧数)。 |
video.facingMode |
指定所需的摄像头方向。可以是 "user"(前置摄像头)或 "environment"(后置摄像头)。 |
function getUserMedia() {
navigator.mediaDevices.getUserMedia({
video: {facingMode: facingMode},
audio: true
}).then(stream => {
localStream = stream
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = stream;
});
}
连接管理
不采用原生webrtc 进行管理连接,原生 API 使用起来较为复杂 ;使用peerJs 组件进行管理连接,PeerJS 在 WebRTC 的基础上提供了更高层次的抽象,使得开发者能够更轻松地构建基于点对点通信的应用程序。
什么是 PeerJS?
PeerJS 是一个基于 WebRTC 技术的 JavaScript 库,用于简化点对点(peer-to-peer)通信的实现。 WebRTC 是一种在浏览器之间实现实时音视频通信的开放标准,但其原生 API 使用起来较为复杂。PeerJS 在 WebRTC 的基础上提供了更高层次的抽象,使得开发者能够更轻松地构建基于点对点通信的应用程序。 PeerJS 提供了以下功能和特性:
- 简化的 API:PeerJS 提供了简洁的 API,使得创建和管理对等连接(peer connection)变得更加容易。
- 自动信令服务:PeerJS 使用一个信令服务器来协助对等连接的建立。开发者无需自行设置信令服务器,PeerJS 提供了默认的信令服务器供使用。
- 可靠的数据通道:PeerJS 提供了可靠的数据通道,使得在对等连接中传输数据变得简单可靠。
- 兼容性:PeerJS 支持主流的现代浏览器,并提供了跨平台的支持,包括桌面浏览器和移动浏览器。
PeerJS 的使用示例:
// 创建 Peer 对象
const peer = new Peer('my-peer-id', {
host: 'peerjs-server.com',
port: 9000,
path: '/myapp'
});
// 监听连接建立事件
peer.on('open', id => {
console.log('Connected with ID:', id);
});
// 建立对等连接
const conn = peer.connect('another-peer-id');
// 监听连接打开事件
conn.on('open', () => {
conn.send('Hello from peer 1!');
});
// 监听接收到消息事件
conn.on('data', data => {
console.log('Received:', data);
});
peerjs-server
PeerJS Server 可以被部署在您自己的服务器上,或者使用 PeerJS 提供的公共信令服务器。公共信令服务器是免费提供给开发者使用的,但也可以选择自己托管信令服务器以获得更高的灵活性和控制。 使用 PeerJS Server 的好处包括:
- 信令交换:PeerJS Server 用于在对等连接的建立过程中传递信令数据,包括对等连接的元数据、ICE 候选项和 SDP(会话描述协议)等。
- 中继服务器:在某些情况下,对等连接的直接建立可能受到网络环境的限制,需要通过中继服务器进行中转。PeerJS Server 可以作为中继服务器,帮助在无法直接连接的情况下建立对等连接。
- 认证和安全性:PeerJS Server 可以提供认证和安全性机制,确保只有授权的用户可以进行对等连接,并保护数据的隐私和完整性。
如何使用 PeerJS Server:
- 使用 PeerJS 的默认公共信令服务器:PeerJS 提供了一个默认的公共信令服务器,您可以在使用 PeerJS 库时直接使用它。
- 使用npm 下载 npm install peer -g ;运行:** ****peerjs --port 9000 --key peerjs --path /myapp**
- 自己部署 PeerJS Server:您也可以下载 PeerJS Server 的源代码,自行部署在自己的服务器上。PeerJS Server 是基于 Node.js 的,您需要安装 Node.js 环境,并按照 PeerJS Server 的文档进行配置和部署。
PeerJS Server 的源代码可以在 PeerJS 的 GitHub 仓库中找到,您可以在那里获取更多关于 PeerJS Server 的详细信息和配置说明。 需要注意的是,如果您选择使用 PeerJS 的默认公共信令服务器,请遵守 PeerJS 的使用政策和条款,并确保适用于您的特定用途。
广域网连接
由于NAT(网络地址转换)和防火墙的存在,直接在两个设备之间建立点对点连接通常是困难的。为了解决这个问题,WebRTC使用了打洞技术,利用中间服务器作为中继来帮助设备建立直接连接,从而实现端到端的实时通信。 打洞服务通过以下步骤来建立直接连接:
- 设备A和设备B都向打洞服务发送连接请求。
- 打洞服务将设备A和设备B的连接信息(IP地址和端口号)传递给对方。
- 设备A和设备B尝试直接连接对方,通过在NAT和防火墙上创建映射,使得两个设备可以直接通信。
- 如果直接连接失败,设备A和设备B将尝试使用中继服务器进行通信。
打洞服务通常使用一些技术来实现这种直接连接,例如STUN(会话遍历实用工具)和TURN(中继通信)。STUN用于获取设备的公共IP地址和端口号,TURN用于在直接连接失败时提供中继服务器作为备用通信路径。 打洞服务对于WebRTC中的实时通信至关重要,它使得两个设备可以直接通信,无需经过中间服务器的转发,从而实现更低的延迟和更高的性能。 需要注意的是,打洞服务只是WebRTC连接建立的一部分,实际的通信数据传输仍然是直接的点对点连接。
示例Demo
使用webRTC实现一个1V1网页视频通话
引入PeerJs组件
这里使用cdn 的方式与引入,可查看文档使用其他引入方式 <script src="https://unpkg.com/[email protected]/dist/peerjs.min.js"></script> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> 注意: 上面引入了adapter-latest.js组件 **adapter-latest.js** 是一个 JavaScript 库,它是由 WebRTC 团队维护的一个开源项目,旨在提供对不同浏览器之间的 WebRTC API 的规范化和标准化。 WebRTC API 在不同的浏览器中实现存在一些差异,而 adapter-latest.js 的目的是通过封装和规范化这些差异,使得开发者可以更方便地使用 WebRTC API 跨浏览器开发实时通信应用。 adapter-latest.js 提供了以下功能:
- 规范化 API:adapter-latest.js 通过提供规范化的 API,使开发者可以使用统一的方式访问 WebRTC API,无论是在 Chrome、Firefox、Safari 还是其他支持 WebRTC 的浏览器上。
- 自动适配:adapter-latest.js 可以根据浏览器的类型和版本自动适配相关的 WebRTC API 实现,从而隐藏不同浏览器之间的差异。
- Polyfill 功能:对于不支持某些 WebRTC API 的浏览器,adapter-latest.js 可以提供 Polyfill 功能,通过模拟这些 API 的行为来实现兼容性。
adapter-latest.js 直接引入就OK了。
切换前后摄像头
切换前后摄像头需要重新获取视频流并替换原有的视频流 通过 facingMode 属性控制前后摄像头,facingMode :user:前置摄像头;environment 后置摄像头, 关键步骤: stream:视频流 replaceTrack() 替换当前正在使用的轨道
stream.getVideoTracks().forEach(track => {
call.peerConnection.getSenders().forEach((sender) => {
if (sender.track.kind === 'video') {
sender.replaceTrack(track).then(() => console.log('replace'));
}
});
})
ios 微信自带浏览器上webrtc不能正常使用的解决方法
解决方法:
window.onload = () => {
document.addEventListener("WeixinJSBridgeReady", function () {
document.getElementById("localVideo").play();
}, false);
}
<video id="localVideo" preload="auto" autoplay="autoplay"
x-webkit-airplay="true"
playsinline="true" webkit-playsinline="true" x5-video-player-type="h5" x5-video-player-fullscreen="true"
x5-video-orientation="portraint" muted></video>
值得注意的是,WeixinJSBridgeReady这个事件会在页面加载后马上触发,因此,上面的这个代码最好写在window.οnlοad=>(){}函数体中,所以video标签也要提前写在html网页中,不要等webrtc通道建立后再去动态创建video。
完整代码
<!DOCTYPE html>
<html lang="zh">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<head>
<title>WebRTC 视频通话演示</title>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
</head>
<body>
<h1>WebRTC 视频通话演示</h1>
<div>
<p>
我的peerId:<span id="myPid"></span>
</p>
<label for="remotePeerIdInput">对方 Peer ID:</label>
<input type="text" id="remotePeerIdInput">
<button id="callButton">呼叫</button>
<button id="hangupButton">挂断</button>
<button id="muteButton">静音</button>
<button id="switchButton">切换摄像头</button>
</div>
<div style="display: flex">
<div style="background: aquamarine">
<h2>本地视频</h2>
<video id="localVideo" width="720" height="240" width="100%" height="300" preload="auto" autoplay="autoplay"
x-webkit-airplay="true"
playsinline="true" webkit-playsinline="true" x5-video-player-type="h5" x5-video-player-fullscreen="true"
x5-video-orientation="portraint" muted></video>
</div>
<div style="background: bisque">
<h2>远程视频</h2>
<video id="remoteVideo" width="720" height="240" autoplay></video>
</div>
</div>
<script src="https://unpkg.com/[email protected]/dist/peerjs.min.js"></script>
<script>
let peer = null;
// 处理来自远程 Peer 的呼叫请求
let call;
let dataConn;
// 初始化摄像头方向为前置
let facingMode = 'user';
let localStream = null
const localVideo = document.getElementById('localVideo');
const init = () => {
// 初始化 Peer 对象,指定唯一的 ID
peer = new Peer({
/* host: "xxxxxxxx",
path: "/peerjswss/myapp",
port: 443,
debug: 2,
secure: true,*/
});
// 输出 Peer ID 到控制台
peer.on('open', function (peerId) {
console.log('我的 Peer ID 是: ' + peerId);
document.getElementById("myPid").innerText = peerId
});
peer.on('connection', (conn) => {
dataConn = conn
conn.send("连接成功...............")
conn.on('data', function (data) {
console.log('被呼叫方-接收消息--》', data);
if (data === "挂断") {
console.log("被呼叫方信息——》对方挂断电话")
call.close()
call = null
dataConn.close()
dataConn = null
console.log("call", call)
console.log("dataConn", dataConn)
// 清空远程视频元素
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = null;
alert("对方已挂断............")
}
});
});
peer.on('call', async incomingCall => {
console.log("监听呼叫")
// 如果已有呼叫正在进行中,则拒接新的呼叫
if (call && call.open) {
console.log('拒绝新的呼叫');
incomingCall.answer();
incomingCall.close();
return;
}
// 显示确认对话框,以便用户确定是否接听呼叫
const confirmed = window.confirm('来自 ' + incomingCall.peer + ' 的呼叫。是否接听?');
if (!confirmed) {
console.log('呼叫被用户拒绝');
incomingCall.answer();
incomingCall.close();
return;
}
await getUserMedia()
// 接听呼叫,并发送本地媒体流
console.log('接听呼叫');
call = incomingCall;
incomingCall.answer(localStream);
// 处理来自远程 Peer 的媒体流
incomingCall.on('stream', remoteStream => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = remoteStream;
});
// 处理呼叫关闭事件
incomingCall.on('close', () => {
console.log('呼叫被远程 Peer 关闭');
if (call && call.open) {
console.log('关闭已有呼叫');
call.close();
call = null;
}
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = null;
});
});
}
// 获取用户媒体流的函数,根据facingMode参数获取不同摄像头的流
function getUserMedia() {
return new Promise((resolve, reject) => {
// 检查浏览器是否支持 getUserMedia API
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert("getUserMedia is not supported");
reject(new Error("getUserMedia is not supported"));
}
navigator.mediaDevices.getUserMedia({
video: {
// enviroment 后置 | user 前置摄像头
facingMode: facingMode,
// 视频帧率
frameRate: 30,
},
audio: {}
}).then(stream => {
resolve(stream);
localStream = stream
localVideo.srcObject = stream;
// 如果已经存在call,则需要将新的流替换到call中
if (call) {
console.log("---> ", call.peerConnection.getSenders())
stream.getVideoTracks().forEach(track => {
call.peerConnection.getSenders().forEach((sender) => {
if (sender.track.kind === 'video') {
sender.replaceTrack(track).then(() => console.log('replace'));
}
});
})
}
}).catch(error => {
reject(error);
});
})
}
// 切换摄像头的函数
function switchCamera() {
// 切换facingMode
facingMode = facingMode === 'user' ? 'environment' : 'user';
// 重新获取用户媒体流
getUserMedia();
}
// 在需要切换摄像头的地方调用switchCamera函数,例如:
const switchButton = document.getElementById('switchButton');
switchButton.addEventListener('click', switchCamera);
// 当点击“呼叫”按钮时,向远程 Peer 发起呼叫请求
const callButton = document.getElementById('callButton');
const remotePeerIdInput = document.getElementById('remotePeerIdInput');
callButton.addEventListener('click', async () => {
const remotePeerId = remotePeerIdInput.value.trim();
if (!remotePeerId) return;
await getUserMedia()
// 如果已有呼叫正在进行中,则关闭它,并发起新的呼叫请求
if (call && call.open) {
console.log('关闭已有呼叫');
call.close();
}
dataConn = peer.connect(remotePeerId);
dataConn.on('open', function () {
// Send messages
dataConn.send('我要给你打电话了............!');
// Receive messages
dataConn.on('data', function (data) {
console.log('呼叫方接收消息--》', data);
if (data === "挂断") {
console.log("呼叫方接收消息->对方挂断电话")
call.close()
call = null
dataConn.close()
dataConn = null
console.log("call", call)
console.log("dataConn", dataConn)
// 清空远程视频元素
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = null;
alert("对方已挂断............")
}
});
});
// 创建新的呼叫请求,并注册事件监听器
console.log('发起呼叫', localStream);
call = peer.call(remotePeerId, localStream);
console.log("call-->", call)
// 处理来自远程 Peer 的媒体流
call.on('stream', remoteStream => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = remoteStream;
});
// 处理呼叫关闭事件
call.on('close', () => {
console.log('呼叫被远程 Peer 关闭');
if (call && call.open) {
console.log('关闭已有呼叫');
call.close();
call = null;
}
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = null;
// 停止本地媒体流
localStream.getTracks().forEach(track => track.stop());
localVideo.srcObject = null;
});
// 处理呼叫错误事件
call.on('error', error => {
console.error('呼叫错误:', error);
});
});
// 当点击“挂断”按钮时,关闭呼叫并停止本地媒体流
const hangupButton = document.getElementById('hangupButton');
hangupButton.addEventListener('click', () => {
dataConn.send("挂断")
// 如果已有呼叫正在进行中,则关闭它
if (call && call.open) {
console.log('挂断呼叫');
call.close();
call = null;
}
// 停止本地媒体流
localStream.getTracks().forEach(track => track.stop());
localVideo.srcObject = null;
// 清空远程视频元素
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = null;
});
// 当点击“静音”按钮时,切换音频静音状态
const muteButton = document.getElementById('muteButton');
muteButton.addEventListener('click', () => {
const audioTracks = localStream.getAudioTracks();
audioTracks.forEach(track => {
track.enabled = !track.enabled;
});
muteButton.innerText = audioTracks[0].enabled ? "静音" : "取消静音";
});
init()
</script>
</body>
</html>
粉丝福利,博主耗时2个月整理了一份详细的音视频开发学习路线,涵盖了音视频开发FFmmpeg、流媒体客户端、流媒体服务器、WebRTC、Android NDK开发、IOS音视频开发等等全栈技术栈,并提供了配套的免费领取C++音视频学习资料包、技术视频/代码,内容包括(FFmpeg ,WebRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs流媒体服务器,音视频通话等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓