前言
- 本文首先会介绍webrtc中的一些基本概念,并通过示例代码一步步完成一个简单的本地视频模拟通话,不通过网络转接。
- 示例代码及其重要,是完整实践配合基础概念的分部介绍。
- 每一段话请注意仔细理解,我会尽可能简单的介绍基础概念。
- 请确保你的电脑是有摄像头的,台式机没有摄像头外接一个就可以
媒体设备
在开发Web时,WebRTC标准提供了API,用于访问连接到计算机或智能手机的相机和麦克风。这些设备通常称为媒体设备,可以通过实现MediaDevices
接口的navigator.mediaDevices
对象使用JavaScript进行访问。通过该对象,我们可以枚举所有已连接的设备,侦听设备更改(连接或断开设备时),并打开设备以检索媒体流
调用getUserMedia()
将触发权限请求。如果用户接受许可,则通过包含一个视频和一个音轨的MediaStream
来解决承诺。如果权限被拒绝,则抛出PermissionDeniedError
。如果没有连接匹配的设备,则会抛出NotFoundError
。
- 获取媒体流示例代码
// createMedia
async createMedia() {
let streamTep = null;
// 保存本地流到全局
streamTep = await navigator.mediaDevices.getUserMedia({audio: true, video: true})
console.log("streamTep",streamTep)
return streamTep;
},
着重点:navigator.mediaDevices.getUserMedia()
函数
- 本地播放媒体流
<div style="float: left">
<video id="sucA"></video>
</div>
// 本地摄像头打开
async nativeMedia(){
const that = this;
that.localStream = await this.createMedia()
let video = document.querySelector('#sucA');
// 旧的浏览器可能没有srcObject
if ("srcObject" in video) {
video.srcObject = that.localStream;
} else {
video.src = window.URL.createObjectURL(that.localStream);
}
// eslint-disable-next-line no-unused-vars
video.onloadedmetadata = function(e) {
video.play();
};
that.initPeer(); // 获取到媒体流后,调用函数初始化 RTCPeerConnection
},
着重点:播放本地媒体流实质就是将上一步拿到的媒体流赋值
到对应的媒体标签组件,在这里就是给video
标签
- 媒体设备约束条件
//比如设置视频窗口的范围
{
"video": {
"width": {
"min": 640,
"max": 1024
},
"height": {
"min": 480,
"max": 768
}
}
}
//获取手机端前置摄像头
{ audio: true, video: { facingMode: "user" } }
//后置摄像头
{ audio: true, video: { facingMode: { exact: "environment" } } }
//具有带宽限制的WebRTC传输,可能需要较低的帧速率
{ video: { frameRate: { ideal: 10, max: 15 } } }
着重点:通俗的讲就是对音频或者视频进行约束,约束条件参数入口就是上面示例中的获取媒体流的函数 getUserMedia({audio: true, video: true})
名词:ICE,STUN,NAT,TURN,SDP解释
交互式连接建立(ICE)是允许您的Web浏览器与对等方连接的框架。A到B两个客户端能够点对点通信的基础就是建立通信,但是很多情况下两个设备之间并没有公网IP,而且还有防火墙这些阻断数据传输,这个时候就需要STUN来为你提供一个独一无二的地址和TURN服务器中继数据。
更详细的介绍请看 MDN,具体不再介绍
RTCPeerConnection
该RTCPeerConnection接口表示本地计算机和远程对等方之间的WebRTC连接。它提供了连接到远程对等方,维护和监视连接以及在不再需要连接时关闭连接的方法。
RTCPeerConnection之间如何建立
- 本地流获取(上述已解释)
- 全局参数初始化
--------------------全局初始化PeerConnection--------------------------
var PeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection;
-----------------------iceServers stun和turn服务器配置-------------------------------------------
var iceServers = {
iceServers: [
{ url: "stun:stun.l.google.com:19302"},// 谷歌的公共服务
{
url: 'turn:numb.viagenie.ca',
credential: 'muazkh',
username: 'webrtc@live.com'
}
]
};
- 初始化两个模拟客户端
//以下pc和pc2 分别代表两个模拟客户端的链接服务简写 ( pc: 代表pc->pc2链接 pc2:代表pc2->pc链接 )
const that = this;
that.pc = new PeerConnection(iceServers);
that.pc2 = new PeerConnection(iceServers);
//将全局视频流赋给pc链接服务
that.pc.addStream(this.localStream);
//监听ice候选信息 具体下面会说明
that.pc.onicecandidate = function(event) {
console.log("pc onicecandidate",event)
if (event.candidate) {
//一般来说这个地方是通过第三方(socket后面会将网络端点对点)发送给另一个客户端,但是现在本地演示直接将候选信息发送到pc2链接服务
that.pc2.addIceCandidate(event.candidate.toJSON());
}
};
//监听远程视频 pc充当呼叫端,所以只要监听pc2有无视频流信息进来
that.pc2.onaddstream = (event) => {
console.log("onaddstream",event)
//监听到流后将视频流赋给另一个video标签
let video = document.querySelector('#sucB');
video.srcObject = event.stream;
video.onloadedmetadata = function(e) {
console.log(e)
video.play();
};
};
onicecandidate
: 候选ICE描述了WebRTC能够与远程设备进行通信所需的协议和路由。在启动WebRTC对等连接时,通常在连接的每一端都建议多个候选对象,直到他们相互同意描述他们认为最好的连接的候选对象为止。
- 呼叫端模拟呼叫(pc充当呼叫端)
//创建offer
async createOffer() {
const that = this;
//创建offer
let offer_tep = await that.pc.createOffer(this.offerOption);
console.log("offer_tep",offer_tep)
//设置本地描述
await that.pc.setLocalDescription(offer_tep)
//接收端设置远程 offer 描述
await that.pc2.setRemoteDescription(offer_tep)
// 接收端创建 answer
let answer = await that.pc2.createAnswer();
// 接收端设置本地 answer 描述
await that.pc2.setLocalDescription(answer);
// 发送端 设置远程 answer 描述
await that.pc.setRemoteDescription(answer);
},
//呼叫
async callA() {
const that = this;
//创建offer 并保存本地描述
await that.createOffer()
},
为何呼叫会有这么麻烦的步骤呢?这就又涉及到webrtc的会话
了,具体看下面一条
webrtc会话
“当用户(上述pc)向另一个用户(上述pc2)发起WebRTC呼叫时,会创建一个特殊的描述,称为offer。此描述包括有关呼叫者为呼叫建议的配置的所有信息。然后,接收者用一个答案来回应,这是他们通话结束的描述。以此方式,两个设备彼此共享为了交换媒体数据所需的信息。这种交换是使用交互式连接建立(ICE)处理的,该协议允许两个设备使用中介程序交换要约和答复,即使两个设备之间都被网络地址转换(NAT)隔开。然后,每个对等方都保留两个描述:本地描述(描述自己)和远程描述(描述呼叫的另一端)”
上面的话简单来说就是 A呼叫B,A创建offer,在本地保留offer,然后发送给B,B创建应答,之后本地保留应答,再将应该发送给A,A拿到后将B的应该设置为本地的远程描述。
- 图示
后续
- 本文是webrtc实现群聊系列文章的第一篇,持续更新中
- 文章来源公众号
苏克分享