需要使用uni-app
技术开发一个类似微信一样的实时音视频通话功能,经过大量的调研和尝试,最终有存在两个方案:第一个方案是使用WebRTC
技术实现P2P
点对点实时通信;第二个方案是使用现成的阿里、腾讯、声网等平台相关产品。
第二种方案无疑是最稳定的,但价格太劝退了,所以退而求其次使用WebRTC
技术实现,下面是在uni-app
技术中实现WebRTC
的记录。
完整代码请在公众号【全栈开发日记】后台回复“WebRTC”获取。
webview中html必须引入uni.webview.js
文件。uniapp有提供的官方下载地址,去官网找一下。
<script type="text/javascript" src="js/webview.js"></script>
uniapp发送代码如下:
<template>
<view class="container">
<web-view :src="webSrc" ref="webview" @onPostMessage="handlePostMessage" @message="handlePostMessage"></web-view>
</view>
</template>
<script>
onLoad() {
/* #ifdef APP-PLUS */
this.webSrc = plus.io.convertLocalFileSystemURL('_www/hybrid/html/camera.html');
var currentWebview = this.$scope.$getAppWebview()
setTimeout(()=>{this.wv = currentWebview.children()[0];},1000)
/* #endif */
},
methods: {
sendWebViewMessage(event){
this.wv.evalJS("receiveAppMessage('" + JSON.stringify(event) + "')")
},
}
</script>
这里省略data中的数据。需要注意的是必须延时一秒再设定this.wv
。
webview接收代码如下:
function receiveAppMessage(event) {
event = JSON.parse(event);
}
webview发送代码如下:
function sendAppMessage(event) {
uni.postMessage({
data: event
});
}
uniapp接收代码如下:
<template>
<view class="container">
<web-view :src="webSrc" ref="webview" @onPostMessage="handlePostMessage" @message="handlePostMessage"></web-view>
</view>
</template>
handlePostMessage: function (event) {
console.log('接收webview发送的消息:', JSON.stringify(event.detail.data));
},
为了形象的表达建立WebRTC连接的整个过程,可以结合下面的时序图对照着文字代码部分进行理解。
WebRTC时序图
发送视频请求使用的是HTTP请求,服务端接收到请求后再通过WS推送给被呼叫方,被呼叫方被动进入视频界面。同时呼叫方建立RTC中独有的WS连接,也就是说现在呼叫方除了软件建立的WS连接,还有RTC的WS连接,用于后续的音视频通话。
如果被呼叫方接收到了呼叫请求,则建立RTC中独有的WS连接。
被呼叫方受到视频请求时,界面出现接通或拒接两个选项,如果被呼叫方选择接通,则通过RTC中独有的WS连接发送给呼叫方告知被呼叫方接受了视频请求。
呼叫方收到了被呼叫方接受了视频请求的消息后,也向被呼叫方回复一条呼叫方知道了被呼叫方准备建立RTC连接的消息。
被呼叫方收到呼叫方的回复后,知道了呼叫方已经做好准备了,于是创建peer连接:
this.createPeerConnection();
peer.createOffer(createOfferAndSendMessage, handleCreateOfferError);
// 创建RTCPeerConnection对象
function createPeerConnection() {
const configuration = {
iceServers: [
{
urls: ["stun:这里是stun服务地址:3478"]
},
{
urls: "turn:这里是turn服务地址:3478",
username: "turn服务账号",
credential: "turn服务密码"
}
]
};
peer = new RTCPeerConnection(configuration);
// 下面两个方法在3.9中补充
peer.onicecandidate = handleIceCandidate;
peer.onaddstream = handleRemoteStreamAdded;
for (const trac of localStream.getTracks()) {
peer.addTrack(trac, localStream);
}
}
// 被呼叫方创建offer并发送offer给呼叫方
function createOfferAndSendMessage(sessionDescription) {
peer.setLocalDescription(sessionDescription).then(() => {
ws.send(JSON.stringify(
{
type: 'TO_ONE',
uid: this.getCurrentUid(),
to: this.getCallUid(),
content: {
type: "PASSIVE_OFFER",
callUid: this.getCurrentUid(),
offer: sessionDescription
}
}
));
})
}
呼叫方收到被呼叫方的offer后先创建自己的RTCPeerConnection
对象,然后根据被呼叫方发来的offer设置远程连接。
// createPeerConnection方法和上面的一样
this.createPeerConnection();
// message.offer就是被呼叫方发送过来的
peer.setRemoteDescription(new RTCSessionDescription(message.offer));
呼叫方接受了offer后需要发送一份应答给被呼叫方,以让被呼叫方也知道呼叫方的地址。
peer.createAnswer().then(createAnswerAndSendMessage, handleCreateAnswerError);
// 呼叫方创建offer应答
function createAnswerAndSendMessage(sessionDescription) {
peer.setLocalDescription(sessionDescription);
ws.send(JSON.stringify(
{
type: 'TO_ONE',
uid: this.getCurrentUid(),
to: this.getCallUid(),
content: {
type: "ACTIVE_ANSWER",
callUid: this.getCurrentUid(),
answer: sessionDescription
}
}
))
}
被呼叫方接收到呼叫方的应答后,根据呼叫方的应答创建RTCSessionDescription
对象。
// 这里的message.answer是呼叫方发送来的应答
peer.setRemoteDescription(new RTCSessionDescription(message.answer));
在步骤3.5
中创建RTCPeerConnection
对象是指定了handleIceCandidate
和handleRemoteStreamAdded
两个方法。一个是指定如果接收到了远程的视频流后如何处理,另一个是如何处理本地的视频流。
// 处理远程视频流
const handleRemoteStreamAdded = (event) => {
remoteStream= event.stream;
smallVideo.srcObject = remoteStream;
smallVideo.play()
}
// 处理ICE候选
const handleIceCandidate = (event) => {
if (event.candidate) {
ws.send(JSON.stringify(
{
type: 'TO_ONE',
uid: this.getCurrentUid(),
to: this.getCallUid(),
content: {
type: "CANDIDATE",
callUid: this.getCurrentUid(),
candidate: event.candidate
}
}
))
}
}
即微信视频中时暂时停止视频画面的传输。
localStream.getVideoTracks()[0].enabled = false;
重新开启摄像头视频的传输重新设置为true
即可。
即微信视频中时暂停麦克风的输入。
localStream.getAudioTracks()[0].enabled = false;
重新开启麦克风的传输重新设置为true
即可。
WebRTC效果图