大家好,我是刘布斯。
随着我们的生活越来越数字化,实时通信变得更重要,而 WebRTC
技术正在重新定义我们对实时通信的理解。
无论是视频会议、在线游戏、远程教育还是医疗保健,WebRTC
都为这些场景提供了简单、高效、安全的解决方案。
作为一名前端的开发者,需要关注 WebRTC
所带来的变革。
让我们今天一块来感受 WebRTC 的魅力!
WebRTC (Web Real-Time Communications
) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer
)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer
)的数据分享和电话会议成为可能。
注意:摄像头和麦克风属于用户的隐私设备,在调用API的时候需要满足安全源的访问(chrome官方文档),即只能是在
HTTPS
协议或localhost
下使用
以前的版本中使用 navigator.getUserMedia
来获取计算机的摄像头或者麦克风,但是现在这个接口废弃,变更为 navigator.mediaDevices.getUserMedia
getUserMedia
除了可以获取默认摄像头和麦克风,还可以控制获取到媒体的分辨率,以及其他的以一些可选项。
注意设备信息中,音频、视频的输入/输出信息里,ID字段是设备信息的核心,后续对媒体的控制都需要用到。
function initInnerLocalDevice() {
const that = this
const localDevice = {
audioIn: [],
videoIn: [],
audioOut: [],
}
const constraints = { video: true, audio: true }
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.log('浏览器不支持获取媒体设备')
return
}
navigator.mediaDevices
.getUserMedia(constraints)
.then(function (stream) {
stream.getTracks().forEach((trick) => {
trick.stop()
})
// List cameras and microphones.
navigator.mediaDevices
.enumerateDevices()
.then(function (devices) {
console.log('devices', devices)
devices.forEach(function (device) {
const obj = { id: device.deviceId, kind: device.kind, label: device.label }
if (device.kind === 'audioinput') {
if (localDevice.audioIn.filter((e) => e.id === device.deviceId).length === 0) {
localDevice.audioIn.push(obj)
}
}
if (device.kind === 'audiooutput') {
if (localDevice.audioOut.filter((e) => e.id === device.deviceId).length === 0) {
localDevice.audioOut.push(obj)
}
} else if (device.kind === 'videoinput') {
if (localDevice.videoIn.filter((e) => e.id === device.deviceId).length === 0) {
localDevice.videoIn.push(obj)
}
}
})
})
.catch(handleError)
})
.catch(handleError)
}
function handleError(error) {
alert('摄像头无法正常使用,请检查是否占用或缺失')
console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name)
}
// 获取音频与摄像头
const constraints = { video: true, audio: true }
function handleError(error) {
console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name)
}
/**
* 获取设备 stream
* @param constraints
* @returns {Promise<MediaStream>}
*/
async function getLocalUserMedia(constraints) {
return await navigator.mediaDevices.getUserMedia(constraints)
}
const stream = await this.getLocalUserMedia(constraints).catch(handleError)
console.log(stream)
/**
* 获取指定媒体设备id对应的媒体流
* @param videoId
* @param audioId
* @returns {Promise<void>}
*/
async function getTargetIdStream(videoId, audioId) {
const constraints = {
audio: { deviceId: audioId ? { exact: audioId } : undefined },
video: {
deviceId: videoId ? { exact: videoId } : undefined,
width: 1920,
height: 1080,
},
}
if (window.stream) {
window.stream.getTracks().forEach((track) => {
track.stop()
})
}
const stream = await this.getLocalUserMedia(constraints).catch(handleError)
}
注意:这里的 constraints
配置和前面getUserMedia
的约束配置是有差别的。在屏幕分享的约束中,video
是不能设置为 false
的,但是可以设置指定的分辨率
/**
* 获取屏幕分享的媒体流
* @returns {Promise<void>}
*/
async function getShareMedia() {
const constraints = {
video: { width: 1920, height: 1080 },
audio: false,
}
if (window.stream) {
window.stream.getTracks().forEach((track) => {
track.stop()
})
}
return await navigator.mediaDevices.getDisplayMedia(constraints).catch((error) => {
console.error('navigator.MediaDevices.getUserMedia error: ', error.message, error.name)
})
}
RTCPeerConnection
是一个由本地计算机到远端的 WebRTC
连接,该接口提供创建,保持,监控,关闭连接的方法的实现,可以简单理解为功能强大的 socket
连接。
RTCPeerConnection
的一些主要用途:音频和视频通信、数据通道、屏幕共享、多对多通信、网络穿透
至于它是如何保障端与端之间的连通性,如何保证音视频的服务质量,又如何确定使用的是哪个编解码器等问题,作为应用者的我们大可不必关心,因为所有的这些问题都已经在 RTCPeerConnection
对象的底层实现好了。
const localPc = new RTCPeerConnection(rtcConfig)
// 将音视频流添加到 RTCPeerConnection 对象中
localStream.getTracks().forEach((track) => { localPc.addTrack(track, localStream)})
在第一步获取音视频流后,需要将流添加到创建的 RTCPeerConnection
对象中,当 RTCPeerConnection 对象获得音视频流后,就可以开始与对端进行媒协体协商。
媒体协商的作用是找到双方共同支持的媒体能力,如双方各自支持的编解码器,音频的参数采样率,采样大小,声道数、视频的参数分辨率,帧率等等。
上述说到的这些音频/视频的信息都会在SDP(Session Description Protocal
:即使用文本描述各端的“能力”) 中进行描述。
一对一的媒体协商大致如下:首先自己在 SDP 中记录自己支持的音频/视频参数和传输协议,然后进行信令交互,交互的过程会同时传递 SDP 信息,另一方接收后与自己的 SDP 信息比对,并取出它们之间的交集,这个交集就是它们协商的结果,也就是它们最终使用的音视频参数及传输协议。
一对一通信中,发起方发送的 SDP 称为Offer(提议),接收方发送的 SDP 称为Answer(应答)。
每端保持两个描述:描述本身的本地描述LocalDescription
,描述呼叫的远端的远程描述RemoteDescription
当通信双方 RTCPeerConnection
对象创建完成后,就可以进行媒体协商了,大致过程如下:
更详细的步骤请参考 MDN 中对会话描述讲解。
信令可以简单理解为消息,在协调通讯的过程中,为了建立一个 WebRTC 的通讯过程,在通信双方彼此连接、传输媒体数据之前,它们要通过信令服务器交换一些信息,如加入房间、离开房间及媒体协商等,而这个过程在 WebRTC 里面是没有实现的,需要自己搭建信令服务。
通过 MDN 先了解下我们需要用到的 API:
createOffer
用于创建 Offer;createAnswer
用于创建 Answer;setLocalDescription
用于设置本地 SDP 信息;setRemoteDescription
用于设置远端的 SDP 信息。下面来看下代码:
RTCPeerConnection
// 配置
export const rtcConfig = null
const localPc = new RTCPeerConnection(rtcConfig)
let offer = await localPc.createOffer()
// 保存为本地描述
await localPc.setLocalDescription(offer)
// 通过信令服务器发送到对端
socket.emit('offer', offer)
socket.on('offer', offer) => {
// 将 Offer 保存为远程描述;
remotePc = new RTCPeerConnection(rtcConfig)
await remotePc.setRemoteDescription(offer)
let remoteAnswer = await remotePc.createAnswer()
await remotePc.setLocalDescription(remoteAnswer)
socket.emit('answer', remoteAnswer)
});
// 发起方接收到 Answer 类型的 SDP 后保存到远程描述,此时发起方也已知道连接双方的配置;
socket.on('answer', answer) => {
// 将 Answer 保存为远程描述;
await localPc.setRemoteDescription(answer);
});
至此,媒体协商结束,紧接着在 WebRTC 底层会收集Candidate,并进行连通性检测,最终在通话双方之间建立起一条链路来。
这个数据通道可以用来发送任何类型的数据,包括文本、文件、二进制数据等。
RTCDataChannel
的工作方式与 WebSocket
非常相似,但是它是在已经存在的 RTCPeerConnection
上建立的,这意味着它继承了 RTCPeerConnection
的连接特性。
RTCDataChannel
的主要用途包括:文本聊天、文件共享、游戏、实时控制。
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。
老规矩,也给我们团队的辅导服务打个广告。