前言
刚写了篇基于WebRTC使用RTCDataChannel接口实现双向数据通信的文章,但是,WebRTC不仅仅可以实现数据通信,还可以实现音视频通信,那么,当前写这篇文章的目的就是为了实现这个音视频及数据通信的Demo。
一、WebRTC的组成?
- getUserMedia
- 为一个RTC连接获取设备的摄像头与(或)麦克风权限,并为此RTC连接接入设备的摄像头与(或)麦克风的信号。
- RTCPeerConnection
- 用于配置音频或视频聊天。
- RTCDataChannel
- 用于设置两个浏览器之间的端到端 数据连接。
二、信令交换的方式
由于WebRTC通信中,发送者和接受者要联通,需要交换信令,而这个信令交换的方式多种多样,而我基于WebRTC使用RTCDataChannel接口实现双向数据通信这篇文章使用的是WebSocket,但是现在觉得,把Demo弄得复杂了不说,还把流程弄得更混乱了。讲WebRTC就讲WebRTC就行了,我们就从简单的方式入手就行。
比较特殊的是,当我们有这个想法的时候,我们也发现,WebRTC交换信令并没有对我们做出任何限制。所以,我们就基于一个最简单的方式,直接把发送者的本地描述和ICE,通过我们的QQ啊微信啊之类的等等,发送到接受者,然后接受者做出相应设置,然后由接受者输出响应,并以同种方式返回, 直到WebRTC对等连接打开。当然这将带来非常高的延迟,但是确实可行的。
三、会话描述
当你阅读完第一、二部分的内容的时候,你应该掌握了相关的基础知识和我们当前Demo的一些猜想。下面我们来详细说下我们Demo当中的整个会话流程。
- 首项我们发送者和接受者各需建立一个RTCPeerConnection对象,并配置我们的打洞服务器,一个stun服务器和turn服务器。这两个服务器我们可以在网上找到一些免费可用的。
- 往发送者的Peer对象中添加视频流,以实现音视频通信,而接受者创建的时候并不需要,接受者在接收到发送者的Offer的时候,再创建即可。
- 我们需在在Peer对象中,添加几个监听事件,并根据Peer对象创建我们的RTCDataChannel对象,以实现我们的数据通信并添加相应的监听事件。
- 发送者使用createOffer创建offer,并设置为本地描述,然后打印出来这个本地描述。并再Peer的icecandidate监听中打印我们的ICE信息,这两个信息就是我们需要通过文本域设置的远端描述和ICE。
- 接受者设置远端描述为发送者打印的offer之后,打开接受者的音视频流并添加到Peer中去,然后通过createAnswer创建answer,并把这个answer设置为接受者的本地描述,然后把这个本地描述和接受者的icecandidate事件的ICE打印出来,这个就是我们需要设置回发送者的远端描述和ICE。
- 以上操作完成之后,我们会发现我们的音视频流和数据通道都联通了,证明我们的Demo正确的实现了WebRTC。
四、客户端应用
1.HTML
<!doctype html>
<html>
<head>
<title>WebRTC: Simple sample</title>
<meta charset="utf-8">
<link rel="stylesheet" href="main.css" type="text/css" media="all">
<script src="adapter.js"></script>
<script src="main.js"></script>
</head>
<body>
<div class="controlbox">
<button id="connectButton" name="connectButton">
Connect
</button>
<button id="connectanserButton" name="connectanserButton">
ConnectAnser
</button>
<button id="disconnectButton" name="disconnectButton" disabled>
Disconnect
</button>
</div>
<div>
<button id="iceButton" name="iceButton" disabled>
设置ICE
</button>
<textarea id="icetextarea" name="icetextarea" rows="6" disabled></textarea>
</div>
<div>
<button id="offerButton" name="offerButton" disabled>
设置OFFER
</button>
<textarea id="offertextarea" name="offertextarea" rows="6" disabled></textarea>
</div>
<div>
<button id="anserButton" name="anserButton" disabled>
设置ANSER
</button>
<textarea id="ansertextarea" name="ansertextarea" rows="6" disabled></textarea>
</div>
<div class="camerabox">
<video id="received_video" autoplay width="300" height="200" muted></video>
<video id="local_video" autoplay width="300" height="200" muted></video>
</div>
<div class="messagebox">
<label for="message">输入一个消息:
<input type="text" name="message" id="message" placeholder="Message text" inputmode="latin" size=60 maxlength=120
disabled>
</label>
<button id="sendButton" name="sendButton" class="buttonright" disabled>
发送
</button>
</div>
<div class="messagebox" id="receivebox">
<p>收到的消息:</p>
</div>
</body>
</html>
整体看我们的页面分为3块。
- 第一块
由Connect,ConnectAnser,Disconnect三个按钮组成,分别代表发送者连接,接受者连接和断开连接三个功能。 - 第二块
由设置ICE,设置OFFER和设置ANSER三个部分组成,分别用于设置ICE,发送者设置远端描述,接受者设置远端描述。 - 第三块
由接受者音视频流,发送者音视频流和一个发送消息的按钮和接受消息的块组成。
2.JavaScript
- 首先我们需要创建RTCPeerConnection对象,然后设置相关事件监听和发送者音视频流处理
async function connectPeers(type) {
iceButton.disabled = false;
icetextarea.disabled = false;
offerButton.disabled = false;
offertextarea.disabled = false;
anserButton.disabled = false;
ansertextarea.disabled = false;
localConnection = new RTCPeerConnection(configuration);
localConnection.ondatachannel = receiveChannelCallback;
localConnection.oniceconnectionstatechange = e => console.log('oniceconnectionstatechange', localConnection.iceConnectionState, e);
localConnection.onicegatheringstatechange = e => console.log('onicegatheringstatechange', localConnection.iceGatheringState, e);
localConnection.onsignalingstatechange = e => console.log('onsignalingstatechange', localConnection.signalingState, e);
// localConnection.ontrack = handleTrackEvent;
localConnection.onaddstream = function (obj) {
document.getElementById("received_video").srcObject = obj.stream;
};
sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
localConnection.onicecandidate = e => {
if (e.candidate) {
//这个就是我们需要的ICE
console.log('localConnection', JSON.stringify(e.candidate));
// remoteConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
}
}
//发送者需要
if (type == "offer") {
try {
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
document.getElementById("local_video").srcObject = webcamStream;
console.log(webcamStream);
} catch (err) {
console.error(err);
return;
}
localConnection.addStream(webcamStream);
await localConnection.setLocalDescription(await localConnection.createOffer());
//这个是发送者的本地描述
console.log('offer', JSON.stringify(localConnection.localDescription));
}
}
监听datachannel事件,在这个事件里对接受者发送的通信数据做了监听,并在有新数据的情况下,添加一个p标签。
监听addstream事件,当有远端音视频流时,把这个视频流添加到received_video这个video标签里。
使用createDataChannel创建数据通信通道,在这个之上添加open和close事件,以使通道打开时,我们能向远端发送数据。
监听icecandidate事件(会多次调用,选择打印的其中一个就行,如果没连通,则换一个试试),在这个事件中有我们为远端设置的ICE。
判断是否是发送端,如果是发送端,我们获取摄像头视频信息,然后添加到Peer对象上去。
- 由第一步,我们得到了一个offer描本地述信息和ICE的描述信息,此时我们把这两个数据设置到远端的设置ICE和设置ANSER的文本域中去,先设置设置ANSER的文本域,在设置设置ICE的文本域,当设置ANSER之后,我们会得到一个用createAnswer创建的接受者的本地描述,把这个描述和接受者的ICE复制到发送者的设置ICE和设置OFFER中去,此时你就会发现,两个video分别显示两端的视频信息,然后发送消息的按钮也可以发送消息了。
设置ICE
function setIce() {
if (!icetextarea.value) {
alert("请输入");
return;
}
var obj = JSON.parse(icetextarea.value);
console.log(obj);
var candidate = new RTCIceCandidate(obj);
localConnection.addIceCandidate(candidate)
.catch(error => console.log('ice-------', error));
}
设置OFFER
function setOfferSdp() {
if (!offertextarea.value) {
alert("请输入");
return;
}
var obj = JSON.parse(offertextarea.value);
console.log(obj);
var desc = new RTCSessionDescription(obj);
localConnection.setRemoteDescription(desc)
.catch(error => console.log('spd-------', error));
}
设置ANSER
async function setAnserSdp() {
if (!ansertextarea.value) {
alert("请输入");
return;
}
var obj = JSON.parse(ansertextarea.value);
console.log(obj);
var desc = new RTCSessionDescription(obj);
await localConnection.setRemoteDescription(desc);
if (!webcamStream) {
console.log("ss2222222");
try {
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
document.getElementById("local_video").srcObject = webcamStream;
console.log('--------', webcamStream);
} catch (err) {
console.error(err);
return;
}
localConnection.addStream(webcamStream);
}
// Add the camera stream to the RTCPeerConnection
await localConnection.setLocalDescription(await localConnection.createAnswer());
//这个是接受者的本地描述
console.log('anser', JSON.stringify(localConnection.localDescription));
}
五、效果演示
类型 | 演示 |
---|---|
发送者设置 | ![]() |
接受者设置 | ![]() |
发送者效果 | ![]() |
接受者效果 | ![]() |
六、项目地址
总结
WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。