寫此文的動(dòng)力:以前在線教育,雖然使用第三方開發(fā),但是底層使用的是webRTC技術(shù),一直想找時(shí)間研究,最近看到相關(guān)實(shí)現(xiàn),親自擼代碼實(shí)現(xiàn)其中原理。學(xué)習(xí)到以下函數(shù),并其實(shí)現(xiàn)方式:
此文寫得比較粗糙,具體實(shí)現(xiàn)結(jié)合源碼理解
主要幾步:
媒體內(nèi)容的流
一個(gè)流對(duì)象可以包含多軌道,包括音頻和視頻軌道等
能通過(guò) WebRTC 傳輸
通過(guò) 標(biāo)簽可以播放
如何捕獲桌面/窗口流
async function getScreenStream(){
const sources = await desktopCapturer.getSources({types: ['screen']})
navigator.webkitGetUserMedia({
audio:false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sources[0].id,
maxWidth: window.screen.width,
maxHeight: window.screen.height
}
}
// 捕獲成功放在callback中的第一個(gè)參數(shù)
},(stream)=>{
peer.emit('add-stream', stream)
}, (err)=> {
// 這里必須寫,不然報(bào)錯(cuò)
console.log(err)
})
}
復(fù)制代碼
如何播放媒體流對(duì)象
var video = document.querySelector('video')
video.srcObject = stream
video.onloadedmetadata = function(e) {
video.play();
}
復(fù)制代碼
SDP(Session Description Protocol)是一種會(huì)話描述協(xié)議,用來(lái)描述多媒體 會(huì)話,主要用于協(xié)商雙方通訊過(guò)程,傳遞基本信息。
SDP的格式包含多行,每行為<type>=<value>
<type>
:字符,代表特定的屬性,比如v,代表版本
<value>
:結(jié)構(gòu)化文本,格式與屬性類型有關(guān),UTF8編碼
第一步:創(chuàng)建RTCPeerConnection,它是我們建立P2P連接的一個(gè)封裝的對(duì)象,然后我們會(huì)調(diào)用RTCPeerConnection的方法去創(chuàng)建一個(gè)offer,這個(gè)offer它是一個(gè)SDP,SDP本質(zhì)上就是一個(gè)協(xié)議。等下展開講SDP,大家可以理解成,我們發(fā)起了一個(gè)邀請(qǐng),我們將我們得邀請(qǐng)?jiān)O(shè)置到我們的LocalDescription,大家可以簡(jiǎn)單理解這三步,其實(shí)就是我們?cè)趧?chuàng)建一個(gè)初始連接,初始的P2P連接。
然后我們需要將我們控制端生成的邀請(qǐng),也就是我們的offer,通過(guò)其他的媒介,比如你可以直接通過(guò)微信或者說(shuō)短信都可以,將邀請(qǐng)的SDP傳到給我們的傀儡端,我們的傀儡端拿到了offer之后,它也會(huì)去創(chuàng)建一個(gè)PeerConnection的對(duì)象,然后我們的傀儡端因?yàn)橐窒砦覀兊漠嬅?,在桌面共享的那一?jié)的時(shí)候其實(shí)我們講過(guò),我們需要將我們的桌面流捕獲完之后添加到我們的PeerConnection里面,隨后我們將控制端設(shè)置為我們傀儡端的遠(yuǎn)端。就是我們要傳給offer對(duì)應(yīng)的PeerConnection,就調(diào)用setRemoteDescription這個(gè)方法,然后我們?yōu)榱吮硎疚覀円呀?jīng)確定了,這里面我們會(huì)調(diào)用一個(gè)createAnswer,代表的是一個(gè)我確定的SDP,然后我會(huì)將SDP同樣在傀儡端里設(shè)置上,這時(shí)候我們的傀儡端也會(huì)生成一個(gè)SDP,同樣我們也可以通過(guò)任何的方法,將我們的響應(yīng)SDP傳到控制端,這時(shí)候控制端也會(huì)將這個(gè)SDP設(shè)置為它要傳輸?shù)膶?duì)象。
到這里,控制端和傀儡端已經(jīng)互相設(shè)置為Remote,這個(gè)時(shí)候就會(huì)建立P2P的連接。簡(jiǎn)單總結(jié)一下,就是我們控制端發(fā)起了衣蛾邀請(qǐng),傀儡端在確定邀請(qǐng)之后,把自己的桌面流添加到P2P的連接當(dāng)中,然后同樣返回一個(gè)確定的協(xié)議,最后我們控制端將確定的協(xié)議也設(shè)置上,這樣子的話我們就代表著控制端和傀儡端的P2P連接已經(jīng)可以開始了。
P2P數(shù)據(jù)交換是需要通過(guò)服務(wù)器,比如在美團(tuán)大象,如果你要發(fā)信息的話,我們會(huì)傳到服務(wù)端,然后發(fā)給對(duì)應(yīng)的用戶。如果走P2P連接,肯定會(huì)比走服務(wù)器來(lái)的快而且還更安全。
為什么要做一層服務(wù)端的轉(zhuǎn)發(fā),其實(shí)答案非常多,其中一個(gè)原因就是我們?cè)诙说蕉说耐ㄐ艜r(shí),需要知道對(duì)方的公網(wǎng)IP和端口號(hào),實(shí)際上這不是一件容易的事情,因?yàn)槲覀兊木W(wǎng)絡(luò)環(huán)境里充斥著NAT技術(shù),NAT是網(wǎng)絡(luò)地址轉(zhuǎn)換的一個(gè)縮寫,為什么這個(gè)技術(shù)會(huì)出現(xiàn)呢,如果大家對(duì)網(wǎng)絡(luò)知識(shí)有一定了解的話,IPv4地址早就不夠用了,它不夠用主要有兩個(gè)原因:
于是人類為了解決地址的問(wèn)題,NAT就出現(xiàn)了,在NAT內(nèi)每個(gè)設(shè)備它都會(huì)有一個(gè)獨(dú)立的局域網(wǎng)地址,然后它們?cè)诟饩W(wǎng)連接的時(shí)候會(huì)共用同一個(gè)公網(wǎng)IP,而NAT它負(fù)責(zé)維護(hù)一個(gè)包括本地IP端口和外網(wǎng)IP端口的一個(gè)映射表。
怎么獲取真正的IP和端口呢?
這里面就會(huì)涉及到NAT打洞,基本方法就是由服務(wù)端跟其中一方ClientB建立一個(gè)連接,這時(shí)候NAT里面就會(huì)建立一個(gè)端口號(hào)的內(nèi)外網(wǎng)的一個(gè)映射,之后我們服務(wù)端就可以知道ClientB外網(wǎng)的IP和端口,然后傳給ClientA,最后ClinetA它就可以直接利用NAT打好了洞,然后跟ClientB進(jìn)行一個(gè)通訊。在webRTC里面已經(jīng)有一個(gè)集成好的機(jī)制,就是STUN服務(wù),當(dāng)ClientA和ClientB要做P2P連接的時(shí)候,它首先第一步需要跟我們的服務(wù)器做一個(gè)穿越打洞,然后將打洞的結(jié)果傳到ClientB下,同樣ClientB也需要做一個(gè)類似的操作,這樣子我們通過(guò)服務(wù)器的幫助下,這樣ClientA和ClientB就能拿到對(duì)方真實(shí)公網(wǎng)的IP和端口。
ICE(Interactive Connectivity Establishment)交互式連接創(chuàng)建
優(yōu)先STUN (Session Traversal Utilities for NAT),NAT會(huì)話穿越應(yīng)用程序
備選TURN (Traversal Using Relay NAT) ,中繼NAT實(shí)現(xiàn)的穿透
Full Cone NAT - 完全錐形NAT
Restricted Cone NAT - 限制錐形NAT
Port Restricted Cone NAT 端口限制錐形NAT
Symmetric NAT 對(duì)稱NAT
視頻播放,需要進(jìn)行換址操作
首先我們的控制端,會(huì)先發(fā)起一個(gè)詢址,然后我們的STUN服務(wù)會(huì)將這個(gè)洞打好,然后返回給我們的控制端,這個(gè)時(shí)候控制端就知道自己的外網(wǎng)的IP和端口,隨后我們需要通過(guò)一定的介質(zhì)然后給到傀儡端,這里面跟PeerConnection的SDP傳輸是一樣的,你可以通過(guò)任何的介質(zhì)來(lái)傳輸,像郵件、微信什么都可以,傀儡端拿到了IceEvent之后,它會(huì)通過(guò)addIceCandidate的方法添加我們的代理,這樣的話,我們的傀儡端就知道控制端的一個(gè)外網(wǎng)IP了,類似的傀儡端也會(huì)拿到自己的IP和端口給到控制端,控制端添加ICE代理,這樣子,我們的P2P才是真正的建立成功。
信令承載的作用就是各種轉(zhuǎn)發(fā)
基于webSocket
//控制端
var pc = new RTCPeerConnection();
let dc = pc.createDataChannel('robotchannel', {reliable: false});
// 建立成功
dc.onopen = function() {
console.log('opened')
peer.on('robot', (type, data) => {
dc.send(JSON.stringify({type, data}))
})
}
// 接收消息
dc.onmessage = function(event) {
console.log('message', event)
}
dc.onerror = (e) => {console.log(e)}
復(fù)制代碼
//傀儡端
const pc = new window.RTCPeerConnection();
pc.ondatachannel = (e) => {
console.log('data', e)
e.channel.onmessage = (e) => {
console.log('onmessage', e, JSON.parse(e.data))
let {type, data} = JSON.parse(e.data)
console.log('robot', type, data)
if(type === 'mouse') {
data.screen = {
width: window.screen.width,
height: window.screen.height
}
}
ipcRenderer.send('robot', type, data)
}
}
復(fù)制代碼
具體源碼查看自己的git
面試說(shuō)分3步:
聯(lián)系客服