webrtc 入门第三章 建立连接
一、介绍
1、概述
在前面的章节我们学习了通过webrtc的基本操作实现了获取本地媒体流、音视频的获取与操作。在得到本地的媒体流后我们就需要将本地媒体数据发送到远端,远端街道媒体流后渲染可视化,从而达到通话的目的。
RTCPeerConnection 连接的核心pai接口,使用它可以将本地流发送到远端,同时也可以将远端媒体流发送到本地从而实现连接。在使用过程中需要用到信令服务器中转信息和STUN服务器打桩服务。
二、实践
1、RTCPeerConnection 连接对象
1.RTCPeerConnection 后文简称pc连接对象。本地为Local对象,远端为Remote对象,在一对一音视频通话场景中pc对象总是成对出现。
方法名 | 参数 | 说明 |
---|---|---|
RTCPeerConnection | RTCConfiguration连接配置参数 | RTCPeerConnection接口代表一个本地到远端的webrtc连接,这个连接提供了创建,保持,监控,关闭连接的方法实现,在创建时需要向其传入配置参数,及ICE配置信息 |
pc.createOffer | RTCOfferOptions对象 | 创建提议Offer方法,此方法会返回SDP offer信息,即RTCSessionDescription对象 |
pc.setLocalDescription | RTCSessionDescription 对象 | 设置本地SDP描述信息 |
pc.setRemoteDescription | RTCSessionDescription 对象 | 设置远端SDP描述信息,接收到远端发来的SDP信息后使用本方法 |
pc.createAnswer | RTCAnswerOptions 对象,可选 | 创建应答Answer方法,此方法会返回SDPAnswer信息,即RTCSessionDescription 对象 |
RTCPIceCandidate | wevrtc网络信息,端口,ip等。 | |
pc.addIceCandidate | RTCPIceCandidate对象 | pc连接添加对方法的IceCandidate对象,得到对放的网络地址等 |
2.流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3UNWW3f-1646320648144)(C:\Users\Administrator\Desktop\liucheng.png)]
3、连接流程
WebRTC连接流程比较复杂,学习过程只考虑将本地Peer-A的流发送到远端Peer-B,具体流程如下(A-本地,B-远端)
1.A获取到本地媒体流 MediaStram:通过getUserMedia 方法获取到本地的音视频流数据
2.A生成本地连接对象PC-A:创建一个RTCPeerConnection接口,该接口提供创建、保持、关闭等方法,在设置前需要设置ICE服务器地址
varconfiguration={"iceServers": [{"url": "stun:stun.1.google.com:19302"}]},
that.peerConnA = new RTCPeerConnection(that.configuration)
3.A将本地视频流加入PC-A :
that.localStream.getTracks().forEach((track) => {
that.peerConnA.addTrack(track, that.localStream)
})
4.A创建提议Offer: that.peerConnA.createOffer() 返回一个RTCPSessionDescription对象,主要是SDP信息是会话的描述信息,两个端连接过程中通过他来进行互相的信息交换,达到媒体协商。
5.A-设置本地描述:创建offer成功后设置本地的描述信息 that.peerConnA.setLocalDescription(event)
6.A将Offer发送给B:通常需要一个信令服务器例如websocket 来转发offer数据
7.B生成PC-B对象:同A端一样,B端也要生成一个RCTPeerConnection对象来应答A端发送的Answer,媒体流等
8.B端设置远端描述:当B端接收到来自A端的offer信息后使用setRemoteDescription() 方法设置来自远端A的描述信息
9.B端生成应答Answer信息:B端使用pc.ceateAnswer()方法生成一个应答A端RTCPSessionDescription对象,主要包括SDP信息,应答Answer和提议Offer是成对出现的。
10.B端设置本地描述:B端创建Answer后设置本地的描述信息 that.peerConnB.setLocalDescription(event)
11.B端返回Answer给A端:通过信令服务器将Answer发送给A端
12.A端设置来自B端的Answer描述信息:当A端通过websocket信令服务获得到的Answer信息后,调用that.peerConnA.setRemoteDescription()方法设置远端的描述
13.交换ICE候选地址信息:建立连接时,会回调onicecandidate事件,传递ice候选地址,同样也需要websocket信令服务来做消息转发,对方接受到以后调用addIceCandidate()方法设置接收到的候选地址
14.交换与使用媒体流:当一方执行addTrack后,另一方的RTCPerrConnection会触发track事件回调,在回调事件中可以获得对方的轨道里的媒体流,这样就能播放对方的流媒体。
that.peerConnB.addEventListener("track", that.getRemoteStream)
getRemoteStream: function (event) {
let that = this
if (that.remoteVideo.srcObject !== event.streams[0]) {
that.remoteVideo.srcObject = event.streams[0]
this.remoteVideo.play();
console.log("开始获取远端视频流")
}
}
4、示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RTCPeerConnection 连接测试</title>
</head>
<body>
<div class="app">
<input type="button" title="开始" value="开始" v-on:click="start"/>
<input type="button" title="呼叫" value="呼叫" v-on:click="call"/>
<input type="button" title="挂断" value="挂断" v-on:click="stop"/>
<input type="button" value="同步" v-on:click="canPlay"/>
<hr>
<span>当前使用视频设备:{[videoDeviceName]}</span>
<br> <br>
<span>当前使用音频设备:{[audioDeviceName]}</span>
<hr>
{{/* 本地视频*/}}
<video id="localVideo" class="localVideo" height="240px" width="280px" playsinline autoplay muted></video>
{{/* 远端视频*/}}
<video id="remoteVideo" class="remoteVideo" height="240px" width="280px" playsinline autoplay muted></video>
{{/* 本地mp4文件*/}}
<br> <br>
<hr>
<span>测试本地mp4文件</span>
<br>
<br>
<video id="myVideo" class="myVideo" autoplay="autoplay" width="400" height="200" controls loop muted
v-on:oncanplay="canPlay">
<source src="/static/file/capture.mp4" type="video/mp4">
</video>
</div>
</body>
<script src="/static/js/Vue.2.5.3.js"></script>
<script type="text/javascript">
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
let vm = new Vue({
el: ".app",
delimiters: ['{[', ']}'],
data: {
// 测试我的视频
myVideo: null,
// 本地视频
localVideo: null,
// 本地视频流
localStream: null,
// 远端视频流
remoteVideo: null,
isOpen: false,
videoDeviceName: "",
audioDeviceName: "",
// ICE service地址
configuration: {
"iceServers": [{"url": "stun:stun.1.google.com:19302"}]
},
// peerConnA 本地对象
peerConnA: null,
// peerConnB 远程对象
peerConnB: null,
// 本地视频轨道
videoTracks: [],
// 本地音频轨道
audioTrack: []
},
methods: {
canPlay: function () {
alert("zheli")
let fps = 0;
// 捕捉
if (this.myVideo.captureStream) {
this.localStream = this.myVideo.captureStream(fps)
} else if (this.capture.mozCaptureStream) {
this.localStream = this.myVideo.mozCaptureStream(fps)
} else {
alert("不支持 captureStream")
}
},
stop: function () {
let that = this
that.peerConnB.close()
that.peerConnA.close()
that.peerConnA = null
that.peerConnB = null
console.log("关闭会话")
}
,
start: async function () {
let that = this;
if (that.isOpen) {
return
}
try {
that.localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true})
that.isOpen = true
that.localVideo.srcObject = that.localStream
that.remoteVideo.srcObject = that.localStream
console.log("获取本地流成功", that.localStream)
// 获取设备轨道
that.videoTracks = that.localStream.getVideoTracks()
that.audioTrack = that.localStream.getAudioTracks()
if (that.videoTracks.length > 0) {
that.videoDeviceName = that.videoTracks[0].label
}
if (that.audioTrack.length > 0) {
that.audioDeviceName = that.audioTrack[0].label
}
} catch (e) {
console.log("getUserMedia 错误" + e)
}
}
,
call: async function () {
let that = this;
console.log("开始呼叫")
// 监听返回icecandidate 信息
that.peerConnA = new RTCPeerConnection(that.configuration)
that.peerConnA.addEventListener("icecandidate", that.onIceCandidateA)
that.peerConnB = new RTCPeerConnection(that.configuration)
that.peerConnB.addEventListener("icecandidate", that.onIceCandidateB)
// 监听ICE状态变化
that.peerConnA.addEventListener("iceconnectionstatechange", that.onIceStateChangeA)
that.peerConnB.addEventListener("iceconnectionstatechange", that.onIceStateChangeB)
// 监听track,获取远端视频流视频
that.peerConnB.addEventListener("track", that.getRemoteStream)
// 将本地流加入本地连接
that.localStream.getTracks().forEach((track) => {
that.peerConnA.addTrack(track, that.localStream)
})
// 创建通话offer
try {
console.log("peerConnA 创建offer会话开始")
const offer = await that.peerConnA.createOffer()
await that.onCreateOfferSuccess(offer)
} catch (e) {
console.log("创建会话描述SD失败:", e.toString())
}
}
,
// 创建提议offer成功
onCreateOfferSuccess: async function (event) {
let that = this
// 设置连接描述
console.log("peerConnA 创建offer返回得SDP信息", event.sdp)
console.log("设置peerConnA得本地描述start...")
try {
await that.peerConnA.setLocalDescription(event)
console.log("设置peerConnA得本地描述成功")
} catch (e) {
console.log("设置peerConnA得本地描述错误:", e.toString())
}
console.log("设置peerConnB得远端描述 start")
try {
await that.peerConnB.setRemoteDescription(event)
console.log("设置peerConnB得远端描述成功")
} catch (e) {
console.log("设置peerConnB得远端描述错误:", e.toString())
}
// 开始应答
console.log("peerConnB创建应答 answer start")
try {
const answer = await that.peerConnB.createAnswer()
console.log("peerConnB创建应答成功")
await that.onCreateAnswerSuccess(answer)
} catch (e) {
console.log("peerConnB创建应答错误:", e.toString())
}
}
,
// 创建answer应答成功
onCreateAnswerSuccess: async function (answer) {
let that = this
console.log("peerConnB创建应答answer数据:", answer)
console.log("peerConnA与peerConnB交换应答answer信息 start")
try {
await that.peerConnB.setLocalDescription(answer)
console.log("设置peerConnB得本地answer 应答远端描述成功")
} catch (e) {
console.log("设置peerConnB得本地answer应答描述错误:", e.toString())
}
try {
await that.peerConnA.setRemoteDescription(answer)
console.log("设置peerConnA得远端answer应答描述成功")
} catch (e) {
console.log("设置peerConnA得远端answer应答描述错误:", e.toString())
}
}
,
// 获取远端视频
getRemoteStream: function (event) {
let that = this
console.log("获取远端视频数据如下:")
console.log(event)
if (that.remoteVideo.srcObject !== event.streams[0]) {
that.remoteVideo.srcObject = event.streams[0]
this.remoteVideo.play();
console.log("开始获取远端视频流")
}
}
,
// 监听ICE状态变化事件回调方法
onIceStateChangeA: function (event) {
console.log("监听 peerConnA ICE状态", this.peerConnA.iceConnectionState)
console.log(event)
}
,
// 监听ICE状态变化事件回调方法
onIceStateChangeB: async function (event) {
console.log("监听 peerConnB ICE状态", this.peerConnB.iceConnectionState)
console.log(event)
}
,
onIceCandidateA: async function (event) {
let that = this
try {
if (event.candidate) {
// 直接交换candidate数据,就不需要通过信令服务器传送
await that.peerConnB.addIceCandidate(event.candidate)
console.log("peerConnB IceCandidate----------")
console.log(event)
that.onAddIceCandidateSuccess(that.peerConnB)
}
} catch (e) {
that.onAddIceCandidateError(that.peerConnB, e)
}
console.log("onIceCandidateA data:" + event.candidate)
}
,
onIceCandidateB: async function (event) {
let that = this
try {
if (event.candidate) {
await that.peerConnA.addIceCandidate(event.candidate)
console.log("peerConnA IceCandidate----------")
console.log(event)
that.onAddIceCandidateSuccess(that.peerConnA)
}
} catch (e) {
that.onAddIceCandidateError(that.peerConnA, e)
}
console.log("onIceCandidateB data:" + event.candidate)
},
//
onAddIceCandidateSuccess: function (pc) {
console.log("添加" + this.getPcName(pc) + " IceCandidate 成功")
},
onAddIceCandidateError: function (pc, err) {
console.log("添加" + this.getPcName(pc) + " IceCandidate 失败" + err.toString())
},
getPcName: function (pc) {
return (pc === this.peerConnA) ? "peerConnA" : "peerConnB"
},
}
,
mounted: function () {
this.localVideo = document.getElementById('localVideo');
this.remoteVideo = document.getElementById('remoteVideo');
this.myVideo = document.getElementById('myVideo');
}
})
</script>
</html>
本地 远端同步
在上述程序中未使用信令服务器作交换ICE和SDP数据,而是采用本地直接添加的方式,来实现了RTCPeerConnection的连接流程。
其连接过程也和第三段的流程一样,先获取本地的媒体地址,然后发起协商offer,设置本地描述,收到远端的协商offer后设置远端描述,生成会话offer,设置本地描述后发给提议方,提议方收到应答会话offer后,设置远端描述,整个流程结束。
三、总结
本章介绍了webrtc的连接流程即api,在一对一的对接过程中可以直接使用,其连接过程比较复杂也相当繁琐。学者需要先了解其连接原理和流程,然后再去结合代码即可掌握。另外学者们需要掌握一下几点。
1.对于媒体流的操作转换例如:获取视频的尺寸格式,监听远端视频流的变化,音频大小变化,视频清晰度自适应,编码方式。
2.了解提议(offer)/应答(answer)里的信息:这些是SDP信息包含,如分辨率,格式,编码等。
3.了解Candidate信息:这些也是SDP信息,里面包括媒体协商的信息,主要包括服务信息,如中继,打桩,服务器的ip和端口文章来源:https://www.toymoban.com/news/detail-409610.html
地址,然后发起协商offer,设置本地描述,收到远端的协商offer后设置远端描述,生成会话offer,设置本地描述后发给提议方,提议方收到应答会话offer后,设置远端描述,整个流程结束。
三、总结
本章介绍了webrtc的连接流程即api,在一对一的对接过程中可以直接使用,其连接过程比较复杂也相当繁琐。学者需要先了解其连接原理和流程,然后再去结合代码即可掌握。另外学者们需要掌握一下几点。
1.对于媒体流的操作转换例如:获取视频的尺寸格式,监听远端视频流的变化,音频大小变化,视频清晰度自适应,编码方式。
2.了解提议(offer)/应答(answer)里的信息:这些是SDP信息包含,如分辨率,格式,编码等。
3.了解Candidate信息:这些也是SDP信息,里面包括媒体协商的信息,主要包括服务信息,如中继,打桩,服务器的ip和端口
4.通过学习视频连接后,可以进行举一反三实现canvas绘画板的同步功能。文章来源地址https://www.toymoban.com/news/detail-409610.html
到了这里,关于webrtc 入门第三章 建立连接的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!