开发步骤:
- 获取本地媒体(getUserMedia(),MediaStream API)
- 在浏览器和对等端(其它浏览器或终端)之间建立对等连接(RTCPeerConnection API)
- RTCPeerConnection 接口代表一个由本地计算机到远端的 WebRTC 连接。该接口提供了创建,保持,监控,关闭连接的方法的实现。
- RTCSessionDescription 接口描述连接或潜在连接的一端的配置方式。每一个RTCSessionDescription 由一个描述类型组成,该描述类型指示它所描述的请求/应答协商过程的SDP 协议的相关描述。RTCSessionDescription 在两个对等点之间协商连接的过程涉及来回交换对象,每个描述都表示描述的发送者支持的连接配置选项的一个组合。一旦两个对等方就连接的配置达成一致,协商就完成了。
- 将媒体和数据通道关联至该连接
- 交换会话描述(RTCSessionDescription)
- 浏览器M从Web服务器请求网页
- Web服务器向M返回带有WebRTC js的网页
- 浏览器L从Web服务器请求网页
- Web服务器向L返回带有WebRTC js的网页
- M决定与L通信,通过M自身的js将M的会话描述对象(offer,提议)发送至Web服务器
- Web服务器将M的会话描述对象发送至L上的js
- L上的js将L的会话描述对象(answer,应答)发送至Web服务器
- Web服务器转发应答至M上的js
- M和L开始交互,确定访问对方的最佳方式
- 完成后,M和L开始协商通信密钥
- M和L开始交换语音、视频或数据
获取本地媒体(getUserMedia
<p>通过getUserMedia()获取视频</p>
<video id="video" autoplay palysinline></video>
<button id="videoButton" onclick="userOpt('video')">打开摄像头</button>
<p>通过getUserMedia()获取音频</p>
<audio id="audio" autoplay controls></audio>
<button id="audioButton" onclick="userOpt('audio')">打开麦克风</button>
<p>通过getUserMedia()获取音视频</p>
<!-- muted 静音 -->
<video id="allVideo" autoplay palysinline muted></video>
<button id="allButton" onclick="userOpt('all')">全部打开</button>
function userOpt(type) {
const constraints = {
audio: false,
video: false
}
switch (type) {
case 'audio':
constraints.audio = true
break
case 'video':
constraints.video = true
case 'all':
constraints.video = true
constraints.audio = true
}
// 注意getUserMedia()在各浏览器中的区别
// Opera --> getUserMedia
// Chrome --> webkitGetUserMedia
// Firefox --> mozGetUserMedia
//navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
handleSuccess(stream, type)
}).catch(handleError)
}
function handleSuccess(stream, type) {
type = type === 'all' ? 'allVideo' : type
const ele = document.querySelector(`#${type}`)
try {
// Chrome浏览器
ele.srcObject = stream
} catch {
// 这里是兼容写法
let compatibleURL = window.URL || window.webkitURL;
ele.src = compatibleURL.createObjectURL(stream);
}
}
function handleError(error) {
console.log('error ', error)
}
建立点对点的信道
WebRTC使用RTCPeerConnection建立连接传送流数据,在建立RTCPeerConnection实例之后,想要建立点对点的信道,需要做两件事:
1. 确定本机上的媒体流的特性,比如分辨率、编解码能力啥的(SDP描述符) - 通过offer和answer交换SDP描述符: - 甲和乙各自建立一个RTCPeerConnection实例pc1、pc2 - 甲通过PC所提供的pc1.createOffer()方法建立一个包含甲的SDP描述符的offer信令 - 甲通过PC所提供的pc1.setLocalDescription()方法,将甲的SDP描述符交给甲的PC实例 - 甲将offer信令通过服务器发送给乙 - 乙将甲的offer信令中所包含的的SDP描述符提取出来,通过PC所提供的pc2.setRemoteDescription()方法交给乙的PC实例 - 乙通过PC所提供的pc2.createAnswer()方法建立一个包含乙的SDP描述符answer信令 - 乙通过PC所提供的pc2.setLocalDescription()方法,将乙的SDP描述符交给乙的PC实例 - 乙将answer信令通过服务器发送给甲 - 甲接收到乙的answer信令后,将其中乙的SDP描述符提取出来,调用pc1.setRemoteDescripttion()方法交给甲自己的PC实例 2. 连接两端的主机的网络地址(ICE Candidate) ICE (交互式连接建立) 是一个被WebRTC使用的框架(跟其他技术在一起使用),它被用在两端之间的连接,不管是何种网络类型 (通常用在视频或语音聊天)。这个协议让两端能够互相找到对方并建立一个连接,即便它们都使用了网络地址转译 (NAT) 去跟内网的其他设备共享了一个公网 IP 地址。 - 通过ICE框架建立NAT/防火墙穿越的连接来获得这个外界可以直接访问的地址,RTCPeerConnection在创立的时候可以将ICE服务器的地址传递进去,如:
const iceServer = {
"iceServers": [{
"url": "stun:stun.l.google.com:19302"
}]
};
const pc = new RTCPeerConnection(iceServer);
- 甲、乙各创建配置了ICE服务器的PC实例,并为其添加onicecandidate事件回调
- 当网络候选可用时,将会调用onicecandidate函数
- 在回调函数内部,甲或乙将网络候选的消息封装在ICE Candidate信令中,通过服务器中转,传递给对方
- 甲或乙接收到对方通过服务器中转所发送过来ICE Candidate信令时,将其解析并获得网络候选,将其通过PC实例的addIceCandidate()方法加入到PC实例中
- 连接创立完成,可以向RTCPeerConnection中通过addStream()加入流来传输媒体流数据
提议/应答协商
要在二者之间建立连接,必须在二者之间建立会话。offer/answer是一种“一次性通过”型协商机制。实际中该过程可能会反复多次。
WebRTC使用RTCSessionDescription对象表示提议和应答。每个浏览器都将生成一个该对象。
本地浏览器只关注两个特定的调用:
// 将我的会话描述告知我的浏览器
pc.setLocalDescription(mySessionDescription)
...
// 将对等端的会话描述告知我的浏览器
pc.setRemoteDescription(yourSessionDescription)
生成提议、应答:
// 生成提议
pc.createOffer(gotOffer, didntGetOffer)
function gotOffer(aSessionDescription) {
setLocalDescription(aSessionDescription)
...
// 现在可以将会话描述(提议offer)发送给对等端,以便对等端
// a)、将提议传递给setRemoteDescription
// b)、调用createAnswer
}
// 生成应答
pc.createAnswer(gotAnswer, didntGetAnswer)
function gotAnswer(aSessionDescription) {
setLocalDescription(aSessionDescription)
...
// 现在将会话描述(应答answer)发送给对等端,以便对等端
// a)、将应答传递给setRemoteDescription
}
数据通道
RTCDataChannel,数据通道是浏览器之间建立的非媒体的交互连接。即不传递媒体消息,绕过服务器直接传递数据。相比WebSocket、http消息,数据通道支持流量大、延迟低。
注意: 单个对等连接中的多个数据通道底层共享一个流,所以只需一次offer、answer即可建立首个数据通道。之后再建立数据通道无需再次进行offer、answer交换。 典型应用:游戏实时状态更新。
pc = new RTCPeerConnection()
dc = pc.createDataChannel(‘')
// 一端创建完数据通道后,另一端只需要监听ondatachannel事件即可:
pc.ondatachannel = function(e) {
dc = e.channel
// 此时,两个对等端已经彼此建立数据通道,可以直接相互发送消息:
dc.send('i am a text string for sending’)
dc.onmessage = function(e) {
console.log('收到消息:', e.data)
}
}