操作系统准备
- 需要准备好摄像头,由于本电脑没有摄像头,这里使用虚拟摄像头(VCam)来代替真实摄像头
效果展示
- 本地台式机使用VCam虚拟摄像头来模拟视频流
- 远程笔记本访问同一链接,从而进行通信
代码提取
- 链接: https://pan.baidu.com/s/13BTXMQWJcPZqihgfN3jgZA
- 提取码: kyfu
WebRTC通信实现明细
- 该文章代码是在 WebRTC本地实现 - Vue与Socket IO通信项目搭建与测试(https)基础上进行更改操作
- 原文章修改的代码内容如下【可查看本文章: 2 - 流程代码】
- 删除原来的发送信息的按钮和test方法
- 增加方法 sendMessage
// 发送消息给 Socket IO 服务器
sendMessage(msg) {
this.$socket.emit('message', sendMessage)
}
代码编写流程
- 客户Peer - A 和 Peer - B进行通信,有可能是A先发起 或者 B先发起,所以代码发起端跟接收端都得写
- Connect Signal Server:2端 连接到信令服务器,也就是之前与Socket IO进行通信过程
- 流程图片
- 流程说明
A用户连接到Sinal Server(也就是Socket IO 服务器)
B用户连接到Sinal Server(也就是Socket IO 服务器)
- getUserMedia:2端 获取本地媒体流
- 流程图片
流程说明
A用户本地的媒体流,并设置localStream
B用户本地的媒体流,并设置localStream流程代码(this.$lib.logInfo等可直接替换为console.log)
本地视频流
远程视频流
- 效果展示(如果 VCam播放视频,这里显示的是视频的内容)
- 1端 create,1端 join:
- 流程图片
流程说明
A为创建者,B为加入者(2个扮演的角色可调换)
A用户创建房间(自定义房间名称:room_webrtc)
同时Signal Server(Socket IO服务器)通知A用户,你是创建者
B用户加入到room_webrtc
同时Signal Server(Socket IO服务器)通知A用户有新的用户加入,通知B用户加入成功流程代码
data() return 中增加变量
// 房间名称
roomName: 'room_webrtc',
// 通道是否准备就绪
isChannelReady: false
methods:中增加方法:创建加入房间
// 试图创建或加入Socket IO的房间
createOrJoinRoom() {
this.$lib.logInfo('试图创建或加入Socket IO 的房间:' + this.roomName);
this.$socket.emit('create or join', this.roomName);
}
sockets:增加监听多个方法
通知用户(A)为创建者(created)
通知房间其他用户(A)有新伙伴(B)准备加入(join)
通知用户(B)加入成功(joined)
通知房间其他用户(A)新伙伴(B)加入成功(ready)
房间满了(full)
created(room) {
this.$lib.logInfo('您是房间:' + room + ' 的创建者');
// 赋值创建者标志
this.isInitiator = true;
},
join(room) {
this.$lib.logInfo('房间:' + room + ' 有新的用户准备加入');
},
joined(room) {
this.$lib.logInfo('房间:' + room + ' 已经加入成功');
// 加入者,设定通道准备就绪
this.isChannelReady = true;
},
ready(){
this.$lib.logInfo('房间:' + room + ' 新的用户已经加入成功');
// 创建者,设定通道准备就绪
this.isChannelReady = true;
},
full(){
this.$lib.logInfo('房间:' + room + ' 已经满员了');
}
- create 端(创建者端,发送offer)
- 流程图片
流程说明
当远程伙伴加入成功后,创建端需开始创建 RTCPeerConnection(本地端机器与远端机器的一条对等连接)
创建Offer 并 设置本地描述(SetLocalDescription)流程代码
data() return 中增加变量
// RTCPeerConnection,本地端机器与远端机器的一条对等连接
pc: null,
// coturn配置供 RTCPeerConnection 使用,(如果没有coturn,可参照demo的step-05中的代码获取)
pcConfiguration: {
iceServers: [{
urls: 'turn:wzeros.cn',
username: 'wzeros',
credential: '123456'
}]
},
// 远程媒体流
remoteStream: null,
methods:中增加方法:创建RTCPeerConnection
// 创建本地端机器与远端机器的一条对等连接
createPeerConnection() {
try {
this.$lib.logInfo('创建 RTCPeerConnection');
this.pc = new RTCPeerConnection(this.pcConfiguration);
this.pc.onicecandidate = this.handleIceCandidate;
this.pc.onaddstream = this.handleRemoteStreamAdded;
this.pc.onremovestream = this.handleRemoteStreamRemoved;
this.$lib.logInfo('增加本地媒体流');
this.pc.addStream(this.localStream);
} catch (e) {
this.$lib.logError('创建 RTCPeerConnection 出错,错误原因如下');
this.$lib.logObj(e.message);
return;
}
},
// 事件触发器
// 只要本地代理ICE 需要通过信令服务器传递信息给其他对等端时就会触发
// 功能说明地址:https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection/onicecandidate
handleIceCandidate(event) {
this.$lib.logInfo('触发 icecandidate 事件,事件内容如下');
this.$lib.logInfo(event);
if (event.candidate) {
this.sendMessage({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
});
} else {
this.$lib.logInfo('End of candidates');
}
},
// 处理远程流添加
handleRemoteStreamAdded(event) {
this.$lib.logInfo('处理远程媒体流添加');
// 赋值远程媒体流
this.remoteStream = event.stream;
// 展示远程媒体流
this.$refs.remoteVideo.srcObject = this.remoteStream;
},
// 处理远程流撤掉
handleRemoteStreamRemoved(event) {
this.$lib.logInfo('Remote stream removed. Event: ', event);
},
// 创建 offer
createOffer() {
this.$lib.logInfo('发送offer给远程的伙伴');
this.pc.createOffer((sessionDescription) => {
this.$lib.logInfo('设置本地描述,描述内容如下:');
this.$lib.logInfo(sessionDescription);
this.pc.setLocalDescription(sessionDescription);
// 通过Signal Server 发送(offer) sessionDescription 到远程伙伴
this.sendMessage(sessionDescription);
}, (event) => {
this.$lib.logError('createOffer()出错,错误原因如下:');
this.$lib.logError(event);
});
}
sockets:ready中增加调用createPeerConnection
ready(room) {
this.$lib.logInfo('房间:' + room + ' 新的用户已经加入成功');
// 创建者 | 加入者,设定通道准备就绪
this.isChannelReady = true;
// 创建者
if (this.isInitiator) {
// 创建本地端机器与远端机器的一条对等连接
this.createPeerConnection();
// 创建 offer
this.createOffer();
}
}
- join端(加入者端,接收offer)
- 流程图片
流程说明
创建者端通过Signal Server(Socket IO服务器)发送出了offer
加入者端在监听(message)消息后,需要初始化RTCPeerConnection等操作
再发送一个回应信息(answer)给create端 并 设置本地描述(SetLocalDescription)流程代码
methods:中增加方法:创建回复
// 创建 answer
createAnswer() {
this.$lib.logInfo('发送answer给远程的创建者');
this.pc.createAnswer().then((sessionDescription) => {
this.$lib.logInfo('设置会话描述,描述内容如下:');
this.$lib.logObj(sessionDescription);
this.pc.setLocalDescription(sessionDescription);
// 通过Signal Server 发送(answer) sessionDescription 到远程的创建者
this.sendMessage(sessionDescription);
}, (error) => {
this.$lib.logError('创建会话描述出错,错误原因如下:');
this.$lib.logObj(error);
});
}
sockets:修改message监听方法
message(msg) {
this.$lib.logInfo('接收到服务端的消息,消息如下:');
this.$lib.logObj(msg);
if (msg.type) {
switch (msg.type) {
// 加入者接收到创建者的offer
case 'offer':
// 创建本地端机器与远端机器的一条对等连接
this.createPeerConnection();
// 设置远程回话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
// 给创建者回复
this.createAnswer();
break;
default:
break;
}
}
}
- create端(创建者端,接收answer)
- 流程图片
流程说明
加入者接收到offer后,返回answer,创建者端接收到answer后设定远程会话描述
设置完会话描述后,此时在创建RTCPeerConnection使用了coturn服务配置的话,将会触发onicecandidate事件。如果建立连接成功后将会触发onaddstream(添加远程媒体流)
在onicecandidate事件中做了一件事情发送type:'candidate'的内容到Signal Server服务器,继而推送到2端流程代码
sockets:修改message监听方法
message(msg) {
this.$lib.logInfo('接收到服务端的消息,消息如下:');
this.$lib.logObj(msg);
if (msg.type) {
switch (msg.type) {
case 'offer': // 加入者接收到创建者的offer
// 创建本地端机器与远端机器的一条对等连接
this.createPeerConnection();
// 设置远程会话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
// 给创建者回复
this.createAnswer();
break;
case 'answer': // 创建者接收到加入者的answer
// 设置远程会话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
break;
default:
break;
}
}
}
- 2端candidate处理
- 流程图片
流程说明
双方只需要做的一件事情就是对RTCPeerConnection添加一个Ice代理后,之后自动触发添加远程媒体流,双方就能够进行通信了流程代码
sockets:修改message监听方法
message(msg) {
this.$lib.logInfo('接收到服务端的消息,消息如下:');
this.$lib.logObj(msg);
if (msg.type) {
switch (msg.type) {
case 'offer': // 加入者接收到创建者的offer
// 创建本地端机器与远端机器的一条对等连接
this.createPeerConnection();
// 设置远程会话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
// 给创建者回复
this.createAnswer();
break;
case 'answer': // 创建者接收到加入者的answer
// 设置远程会话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
break;
case 'candidate': // candidate处理
this.pc.addIceCandidate(new RTCIceCandidate({
sdpMLineIndex: msg.label,
candidate: msg.candidate
}));
break;
default:
break;
}
}
}
- 2端退出通信处理
- 流程图片
流程说明
只要有一方退出系统,发送离开消息到Signal Server服务器,信令服务器通知双方结束通信
关闭RTCPeerConnection,暂停本地视频流和远程视频流展示流程代码
methods:中增加方法:离开通信
leave() {
if(this.remoteStream !== null) {
this.$refs.remoteVideo.srcObject = null;
this.remoteStream = null;
}
if(this.localStream !== null) {
this.$refs.localVideo.srcObject = null;
this.localStream = null;
}
if (this.pc !== null) {
this.pc.close();
this.pc = null;
}
}
export default :中增加beforeDestroy监听
beforeDestroy() {
this.sendMessage('leave');
}
sockets:修改message监听方法
message(msg) {
this.$lib.logInfo('接收到服务端的消息,消息如下:');
this.$lib.logObj(msg);
if (msg.type) {
switch (msg.type) {
case 'offer': // 加入者接收到创建者的offer
// 创建本地端机器与远端机器的一条对等连接
this.createPeerConnection();
// 设置远程会话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
// 给创建者回复
this.createAnswer();
break;
case 'answer': // 创建者接收到加入者的answer
// 设置远程会话描述
this.pc.setRemoteDescription(new RTCSessionDescription(msg));
break;
case 'candidate': // candidate处理
this.pc.addIceCandidate(new RTCIceCandidate({
sdpMLineIndex: msg.label,
candidate: msg.candidate
}));
break;
case 'leave': // 离开通信
this.leave();
break;
default:
break;
}
}
}