基于WebRtc的视频通话聊天
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
首先大概介绍一下,What the WebRTC?(没错,这段我网上抄的,因为官网打不开)
WebRTC(Web Real-Time Communication——网页实时通信)是一个基于浏览器的实时多媒体通信技术。该项技术旨在使Web浏览器具备实时通信能力;同时,通过将这些能力封装并以JavaScript API的方式开放给Web应用开发人员,使得Web应用开发人员能够通过HTML标签和JavaScript API快速地开发出基于Web浏览器的实时音视频应用,而无需依赖任何第三方插件。该项技术将Web浏览器打造成一个适用于在所有用户设备(比如移动智能终端和个人电脑等)间进行实时的音视频和数据通信的通用平台。官网地址: webrtc.org/
在了解这玩意之前,请先熟悉一下流媒体的API,参考这个地址: developer.mozilla.org/en-US/docs/…
还有一个很重要的点,如果你的网站不是https,那么chrome浏览器是默认禁用摄像头以及麦克风的,解决办法:
在chrome浏览器地址栏输入"chrome://flags/#unsafely-treat-insecure-origin-as-secure"
页面输入框中输入你的网站地址,选择Enabled
右下角会有一个按钮出现,点击Relaunch
自动重启即可访问
大致画了一下本次的实现流程
在开始之前请确保你有一个可用的WebSocket服务器,后面发送socket信息用伪代码编写(所有的doSend方法都是给后端的Socket发送消息),这里都是前端相关的内容,WebRTC简单的视频通话不涉及到后端代码,需要后端Socket协助的也只是交换信息,所以这里不展示后端代码
// 用户A发起通话邀请
socket.send({type: 'call'});
// 用户B接受通话邀请,并给A回执
socket.on('call', () => {
_this.$confirm(`用户${messageVo.fromUser}向你发起视频通话`, '提示', {
confirmButtonText: '接听',
cancelButtonText: '挂断',
type: 'warning'
}).then(async () => {
// 接听后初始化本地的Media与Peer
await _this.initLocalMedia();
await _this.initLocalPeer('receive');
_this.calling = true;
_this.otherVideo = true;
let acceptVo = {
messageType: 10008,
type: 'accept',
toUserId: messageVo.fromUserId
};
_this.doSend(acceptVo);
}, () => {
let acceptVo = {
messageType: 10008,
type: 'hang-up',
toUserId: messageVo.fromUserId
};
_this.doSend(acceptVo);
});
});
当用户B接受了A的通话邀请之后,开始准备视频流媒体
async initLocalMedia() {
// 如果这个看不懂,请移步最上方的链接,有流媒体api的介绍
this.localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
// 本地视频展示窗口,一般视频通话会显示远程视频和本地视频
let video = document.querySelector('#local-video');
video.srcObject = this.localStream;
}
当B接受时,准备初始化RTCPeer,而A接收到B的同意通话回执后,也开始初始化RTCPeer,这里通过 'receive' 表示是B的初始化,用于区分,因为ice候选信息只需要A发给B,B进行设置即可
async initLocalPeer(type) {
let _this = this;
// 浏览器差异
let PeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection;
_this.localPeer = new PeerConnection(iceServers);
// 本地视频流
_this.localPeer.addStream(_this.localStream);
_this.localPeer.onaddstream = event => {
// 远程的流媒体展示,这里是建立连接成功后的调用
let video = document.querySelector('#remote-video');
video.srcObject = event.stream;
}
if (type != 'receive') {
// 发起通话方
_this.localPeer.onicecandidate = (event) => {
// 通过 socket 将ice候选发送给对方
if (event.candidate) {
let messageVo = {
sdp: event.candidate,
messageType: 10008,
type: 'ice',
toUserId: _this.currentRoom.receiveId
};
_this.doSend(messageVo);
}
};
}
}
当B接收到ice候选信息时,将ice信息设置进来
_this.localPeer.addIceCandidate(JSON.parse(messageVo.sdp));
A在初始化Peer后,创建 offer并发送给B
async createOffer() {
let _this = this;
let offer = await _this.localPeer.createOffer();
_this.localPeer.setLocalDescription(offer);
let messageVo = {
sdp: offer,
messageType: 10008,
type: 'offer',
toUserId: _this.currentRoom.receiveId
};
_this.doSend(messageVo);
},
B接收offer信息,并生成自己的answer发送给A
// 远程端 offer
_this.localPeer.setRemoteDescription(JSON.parse(messageVo.sdp));
// 接收端创建 answer
let answer = await _this.localPeer.createAnswer();
// 接收端设置本地 answer 描述
_this.localPeer.setLocalDescription(answer);
let answerVo = {
sdp: answer,
messageType: 10008,
type: 'answer',
toUserId: messageVo.fromUserId
};
// 接收端的offer
_this.doSend(answerVo);
A接收B的answer信息并设置,完成连接
_this.localPeer.setRemoteDescription(JSON.parse(messageVo.sdp));
回归到前面,创建 RTCPeer连接时,传入的参数 iceServers ,这是个啥东西呢,众所周知,A和B两台电脑的ip地址并非外网,所以A和B是不能直接通信的,也不能直接连接,所以就需要使用到NAT地址转换,而这里传入的参数结构如下,当然是白嫖的google的公共stun服务,这个stun服务就是实现了NAT穿透的作用。这里也可以自己搭建,非常的简单。
var iceServers = {
iceServers: [
{ url: "stun:stun.l.google.com:19302"}, // 谷歌的公共服务
]
};
至此,一个简单的1对1视频通话就完成了,在获取流媒体的时候关掉视频即可实现语音通话,演示效果可以去陌溪代佬的蘑菇博客里看
==================================================================