webrtc 入門第三章 建立連接
一、介紹
1、概述
? 在前面的章節(jié)我們學(xué)習(xí)了通過webrtc的基本操作實(shí)現(xiàn)了獲取本地媒體流、音視頻的獲取與操作。在得到本地的媒體流后我們就需要將本地媒體數(shù)據(jù)發(fā)送到遠(yuǎn)端,遠(yuǎn)端街道媒體流后渲染可視化,從而達(dá)到通話的目的。
? RTCPeerConnection 連接的核心pai接口,使用它可以將本地流發(fā)送到遠(yuǎn)端,同時(shí)也可以將遠(yuǎn)端媒體流發(fā)送到本地從而實(shí)現(xiàn)連接。在使用過程中需要用到信令服務(wù)器中轉(zhuǎn)信息和STUN服務(wù)器打樁服務(wù)。
二、實(shí)踐
1、RTCPeerConnection 連接對(duì)象
1.RTCPeerConnection 后文簡(jiǎn)稱pc連接對(duì)象。本地為L(zhǎng)ocal對(duì)象,遠(yuǎn)端為Remote對(duì)象,在一對(duì)一音視頻通話場(chǎng)景中pc對(duì)象總是成對(duì)出現(xiàn)。
方法名 | 參數(shù) | 說明 |
---|---|---|
RTCPeerConnection | RTCConfiguration連接配置參數(shù) | RTCPeerConnection接口代表一個(gè)本地到遠(yuǎn)端的webrtc連接,這個(gè)連接提供了創(chuàng)建,保持,監(jiān)控,關(guān)閉連接的方法實(shí)現(xiàn),在創(chuàng)建時(shí)需要向其傳入配置參數(shù),及ICE配置信息 |
pc.createOffer | RTCOfferOptions對(duì)象 | 創(chuàng)建提議Offer方法,此方法會(huì)返回SDP offer信息,即RTCSessionDescription對(duì)象 |
pc.setLocalDescription | RTCSessionDescription 對(duì)象 | 設(shè)置本地SDP描述信息 |
pc.setRemoteDescription | RTCSessionDescription 對(duì)象 | 設(shè)置遠(yuǎn)端SDP描述信息,接收到遠(yuǎn)端發(fā)來的SDP信息后使用本方法 |
pc.createAnswer | RTCAnswerOptions 對(duì)象,可選 | 創(chuàng)建應(yīng)答Answer方法,此方法會(huì)返回SDPAnswer信息,即RTCSessionDescription 對(duì)象 |
RTCPIceCandidate | wevrtc網(wǎng)絡(luò)信息,端口,ip等。 | |
pc.addIceCandidate | RTCPIceCandidate對(duì)象 | pc連接添加對(duì)方法的IceCandidate對(duì)象,得到對(duì)放的網(wǎng)絡(luò)地址等 |
2.流程圖
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-u3UNWW3f-1646320648144)(C:\Users\Administrator\Desktop\liucheng.png)]
3、連接流程
WebRTC連接流程比較復(fù)雜,學(xué)習(xí)過程只考慮將本地Peer-A的流發(fā)送到遠(yuǎn)端Peer-B,具體流程如下(A-本地,B-遠(yuǎn)端)
1.A獲取到本地媒體流 MediaStram:通過getUserMedia 方法獲取到本地的音視頻流數(shù)據(jù)
2.A生成本地連接對(duì)象PC-A:創(chuàng)建一個(gè)RTCPeerConnection接口,該接口提供創(chuàng)建、保持、關(guān)閉等方法,在設(shè)置前需要設(shè)置ICE服務(wù)器地址
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創(chuàng)建提議Offer: that.peerConnA.createOffer() 返回一個(gè)RTCPSessionDescription對(duì)象,主要是SDP信息是會(huì)話的描述信息,兩個(gè)端連接過程中通過他來進(jìn)行互相的信息交換,達(dá)到媒體協(xié)商。
5.A-設(shè)置本地描述:創(chuàng)建offer成功后設(shè)置本地的描述信息 that.peerConnA.setLocalDescription(event)
6.A將Offer發(fā)送給B:通常需要一個(gè)信令服務(wù)器例如websocket 來轉(zhuǎn)發(fā)offer數(shù)據(jù)
7.B生成PC-B對(duì)象:同A端一樣,B端也要生成一個(gè)RCTPeerConnection對(duì)象來應(yīng)答A端發(fā)送的Answer,媒體流等
8.B端設(shè)置遠(yuǎn)端描述:當(dāng)B端接收到來自A端的offer信息后使用setRemoteDescription() 方法設(shè)置來自遠(yuǎn)端A的描述信息
9.B端生成應(yīng)答Answer信息:B端使用pc.ceateAnswer()方法生成一個(gè)應(yīng)答A端RTCPSessionDescription對(duì)象,主要包括SDP信息,應(yīng)答Answer和提議Offer是成對(duì)出現(xiàn)的。
10.B端設(shè)置本地描述:B端創(chuàng)建Answer后設(shè)置本地的描述信息 that.peerConnB.setLocalDescription(event)
11.B端返回Answer給A端:通過信令服務(wù)器將Answer發(fā)送給A端
12.A端設(shè)置來自B端的Answer描述信息:當(dāng)A端通過websocket信令服務(wù)獲得到的Answer信息后,調(diào)用that.peerConnA.setRemoteDescription()方法設(shè)置遠(yuǎn)端的描述
13.交換ICE候選地址信息:建立連接時(shí),會(huì)回調(diào)onicecandidate事件,傳遞ice候選地址,同樣也需要websocket信令服務(wù)來做消息轉(zhuǎn)發(fā),對(duì)方接受到以后調(diào)用addIceCandidate()方法設(shè)置接收到的候選地址
14.交換與使用媒體流:當(dāng)一方執(zhí)行addTrack后,另一方的RTCPerrConnection會(huì)觸發(fā)track事件回調(diào),在回調(diào)事件中可以獲得對(duì)方的軌道里的媒體流,這樣就能播放對(duì)方的流媒體。
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("開始獲取遠(yuǎn)端視頻流")
}
}
4、示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RTCPeerConnection 連接測(cè)試</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>當(dāng)前使用視頻設(shè)備:{[videoDeviceName]}</span>
<br> <br>
<span>當(dāng)前使用音頻設(shè)備:{[audioDeviceName]}</span>
<hr>
{{/* 本地視頻*/}}
<video id="localVideo" class="localVideo" height="240px" width="280px" playsinline autoplay muted></video>
{{/* 遠(yuǎn)端視頻*/}}
<video id="remoteVideo" class="remoteVideo" height="240px" width="280px" playsinline autoplay muted></video>
{{/* 本地mp4文件*/}}
<br> <br>
<hr>
<span>測(cè)試本地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: {
// 測(cè)試我的視頻
myVideo: null,
// 本地視頻
localVideo: null,
// 本地視頻流
localStream: null,
// 遠(yuǎn)端視頻流
remoteVideo: null,
isOpen: false,
videoDeviceName: "",
audioDeviceName: "",
// ICE service地址
configuration: {
"iceServers": [{"url": "stun:stun.1.google.com:19302"}]
},
// peerConnA 本地對(duì)象
peerConnA: null,
// peerConnB 遠(yuǎn)程對(duì)象
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("關(guān)閉會(huì)話")
}
,
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)
// 獲取設(shè)備軌道
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 錯(cuò)誤" + e)
}
}
,
call: async function () {
let that = this;
console.log("開始呼叫")
// 監(jiān)聽返回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)
// 監(jiān)聽I(yíng)CE狀態(tài)變化
that.peerConnA.addEventListener("iceconnectionstatechange", that.onIceStateChangeA)
that.peerConnB.addEventListener("iceconnectionstatechange", that.onIceStateChangeB)
// 監(jiān)聽track,獲取遠(yuǎn)端視頻流視頻
that.peerConnB.addEventListener("track", that.getRemoteStream)
// 將本地流加入本地連接
that.localStream.getTracks().forEach((track) => {
that.peerConnA.addTrack(track, that.localStream)
})
// 創(chuàng)建通話offer
try {
console.log("peerConnA 創(chuàng)建offer會(huì)話開始")
const offer = await that.peerConnA.createOffer()
await that.onCreateOfferSuccess(offer)
} catch (e) {
console.log("創(chuàng)建會(huì)話描述SD失?。?, e.toString())
}
}
,
// 創(chuàng)建提議offer成功
onCreateOfferSuccess: async function (event) {
let that = this
// 設(shè)置連接描述
console.log("peerConnA 創(chuàng)建offer返回得SDP信息", event.sdp)
console.log("設(shè)置peerConnA得本地描述start...")
try {
await that.peerConnA.setLocalDescription(event)
console.log("設(shè)置peerConnA得本地描述成功")
} catch (e) {
console.log("設(shè)置peerConnA得本地描述錯(cuò)誤:", e.toString())
}
console.log("設(shè)置peerConnB得遠(yuǎn)端描述 start")
try {
await that.peerConnB.setRemoteDescription(event)
console.log("設(shè)置peerConnB得遠(yuǎn)端描述成功")
} catch (e) {
console.log("設(shè)置peerConnB得遠(yuǎn)端描述錯(cuò)誤:", e.toString())
}
// 開始應(yīng)答
console.log("peerConnB創(chuàng)建應(yīng)答 answer start")
try {
const answer = await that.peerConnB.createAnswer()
console.log("peerConnB創(chuàng)建應(yīng)答成功")
await that.onCreateAnswerSuccess(answer)
} catch (e) {
console.log("peerConnB創(chuàng)建應(yīng)答錯(cuò)誤:", e.toString())
}
}
,
// 創(chuàng)建answer應(yīng)答成功
onCreateAnswerSuccess: async function (answer) {
let that = this
console.log("peerConnB創(chuàng)建應(yīng)答answer數(shù)據(jù):", answer)
console.log("peerConnA與peerConnB交換應(yīng)答answer信息 start")
try {
await that.peerConnB.setLocalDescription(answer)
console.log("設(shè)置peerConnB得本地answer 應(yīng)答遠(yuǎn)端描述成功")
} catch (e) {
console.log("設(shè)置peerConnB得本地answer應(yīng)答描述錯(cuò)誤:", e.toString())
}
try {
await that.peerConnA.setRemoteDescription(answer)
console.log("設(shè)置peerConnA得遠(yuǎn)端answer應(yīng)答描述成功")
} catch (e) {
console.log("設(shè)置peerConnA得遠(yuǎn)端answer應(yīng)答描述錯(cuò)誤:", e.toString())
}
}
,
// 獲取遠(yuǎn)端視頻
getRemoteStream: function (event) {
let that = this
console.log("獲取遠(yuǎn)端視頻數(shù)據(jù)如下:")
console.log(event)
if (that.remoteVideo.srcObject !== event.streams[0]) {
that.remoteVideo.srcObject = event.streams[0]
this.remoteVideo.play();
console.log("開始獲取遠(yuǎn)端視頻流")
}
}
,
// 監(jiān)聽I(yíng)CE狀態(tài)變化事件回調(diào)方法
onIceStateChangeA: function (event) {
console.log("監(jiān)聽 peerConnA ICE狀態(tài)", this.peerConnA.iceConnectionState)
console.log(event)
}
,
// 監(jiān)聽I(yíng)CE狀態(tài)變化事件回調(diào)方法
onIceStateChangeB: async function (event) {
console.log("監(jiān)聽 peerConnB ICE狀態(tài)", this.peerConnB.iceConnectionState)
console.log(event)
}
,
onIceCandidateA: async function (event) {
let that = this
try {
if (event.candidate) {
// 直接交換candidate數(shù)據(jù),就不需要通過信令服務(wù)器傳送
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>
本地 遠(yuǎn)端同步
?
在上述程序中未使用信令服務(wù)器作交換ICE和SDP數(shù)據(jù),而是采用本地直接添加的方式,來實(shí)現(xiàn)了RTCPeerConnection的連接流程。
其連接過程也和第三段的流程一樣,先獲取本地的媒體地址,然后發(fā)起協(xié)商offer,設(shè)置本地描述,收到遠(yuǎn)端的協(xié)商offer后設(shè)置遠(yuǎn)端描述,生成會(huì)話offer,設(shè)置本地描述后發(fā)給提議方,提議方收到應(yīng)答會(huì)話offer后,設(shè)置遠(yuǎn)端描述,整個(gè)流程結(jié)束。
三、總結(jié)
本章介紹了webrtc的連接流程即api,在一對(duì)一的對(duì)接過程中可以直接使用,其連接過程比較復(fù)雜也相當(dāng)繁瑣。學(xué)者需要先了解其連接原理和流程,然后再去結(jié)合代碼即可掌握。另外學(xué)者們需要掌握一下幾點(diǎn)。
1.對(duì)于媒體流的操作轉(zhuǎn)換例如:獲取視頻的尺寸格式,監(jiān)聽遠(yuǎn)端視頻流的變化,音頻大小變化,視頻清晰度自適應(yīng),編碼方式。
2.了解提議(offer)/應(yīng)答(answer)里的信息:這些是SDP信息包含,如分辨率,格式,編碼等。
3.了解Candidate信息:這些也是SDP信息,里面包括媒體協(xié)商的信息,主要包括服務(wù)信息,如中繼,打樁,服務(wù)器的ip和端口文章來源:http://www.zghlxwxcb.cn/news/detail-409610.html
地址,然后發(fā)起協(xié)商offer,設(shè)置本地描述,收到遠(yuǎn)端的協(xié)商offer后設(shè)置遠(yuǎn)端描述,生成會(huì)話offer,設(shè)置本地描述后發(fā)給提議方,提議方收到應(yīng)答會(huì)話offer后,設(shè)置遠(yuǎn)端描述,整個(gè)流程結(jié)束。
三、總結(jié)
本章介紹了webrtc的連接流程即api,在一對(duì)一的對(duì)接過程中可以直接使用,其連接過程比較復(fù)雜也相當(dāng)繁瑣。學(xué)者需要先了解其連接原理和流程,然后再去結(jié)合代碼即可掌握。另外學(xué)者們需要掌握一下幾點(diǎn)。
1.對(duì)于媒體流的操作轉(zhuǎn)換例如:獲取視頻的尺寸格式,監(jiān)聽遠(yuǎn)端視頻流的變化,音頻大小變化,視頻清晰度自適應(yīng),編碼方式。
2.了解提議(offer)/應(yīng)答(answer)里的信息:這些是SDP信息包含,如分辨率,格式,編碼等。
3.了解Candidate信息:這些也是SDP信息,里面包括媒體協(xié)商的信息,主要包括服務(wù)信息,如中繼,打樁,服務(wù)器的ip和端口
4.通過學(xué)習(xí)視頻連接后,可以進(jìn)行舉一反三實(shí)現(xiàn)canvas繪畫板的同步功能。文章來源地址http://www.zghlxwxcb.cn/news/detail-409610.html
到了這里,關(guān)于webrtc 入門第三章 建立連接的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!