基于WebRTC实现简单的视频通话流程

311 阅读4分钟

基于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

自动重启即可访问

大致画了一下本次的实现流程
1647415267289.jpg

在开始之前请确保你有一个可用的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视频通话就完成了,在获取流媒体的时候关掉视频即可实现语音通话,演示效果可以去陌溪代佬的蘑菇博客里看

==================================================================