前言
接上篇文章,實現了音視訊通話的轉接mqtt
的實現,接下來進行音影片的具體實現,這裏採用的方案是透過webRTC
進行實現,這個API
的好處就是不用額外安裝其他軟體的前提下,瀏覽器直接點對點實現音視訊通話,現在來簡單介紹下具體實現過程,希望能對你有幫助。
webRTC是什麼
webRTC
是一項實時通訊技術,也叫點對點通訊,它能讓 Web應用程式和 站點能夠流式傳輸音影片媒體,以及瀏覽器之間交換資料而無需中介軟體,建立瀏覽器點對點資料共享和傳輸,而且通道一旦建立,可以不經過服務端,客戶端對客戶端進行實時通話。
準備工作
概念知識
RTCPeerConnection
簡稱peer
,點對點通訊(peer-to-peer),透過這個方法建立一個peer
端,可以理解成建立了一個影片視窗殼子,例如發起方建立一個peer
,接收方也建立一個peer
,兩端之間進行聯通,即可實現實時通話。
offer
透過createOffer
建立的offer資訊,裡面包含SDP offer
資訊,發起方將offer傳送給對方,建立影片連線。
answer
透過createAnswer
建立的應答資訊,和offer
類似,接收方接收到offer
後,透過createAnswer
方法建立本機的SDP
資訊,然後傳送給發起方,這樣雙方的SDP
交換就完成了。
candidate
有用過獲取本機ip
的對這個應該眼熟,獲取本機ip
地址,也是透過webRTC
的candidate
進行實現,這個的主要作用是獲取本機的ip,它會優先抽區域網中獲取,如果區域網獲取不到,然後就會從TURN/STURN
獲取。
信令伺服器
通常使用webSocket
、mqtt
等進行實現,在前面我們有說過建立offer
和answer
傳送給對方,瀏覽器怎麼知道要發給誰?所以這裏就是使用信令伺服器傳送給指定的人。
TURN/STURN
這個俗稱就是打洞,網路穿透,當兩個裝置不處於同一區域網的前提下,就需要這個進行一個網路穿透,透過這個可以獲取兩個裝置各自在公網的ip,這樣就能進行連線互通。
為什麼要交換SDP和信令伺服器
有人可能會問,WebRTC
不是能夠點對點直接通訊嗎?為什麼還需要交換SDP
和信令伺服器?
舉個梨子:張三會中,英,韓語三門語言,李四會中、日、俄語,現在張三和李四想要對話交流,自然而然的找到兩個人共同點語言中文進行交流,同理,交換SDP
其實就是找共同語言的一個過程,也就是取交集。
然後就是信令伺服器,再打個比方,現在有100個房間,我現在只想去張三的房間,但是我又不知道張三的房間號,所以這個時候就需要信令伺服器Socket,將我想去張三房間的這個想法轉達給張三,並帶我去他的房間。
常用方法
API
RTCPeerConnection
:建立一個peer端setLocalDescription
:設定本地描述setRemoteDescription
:設定遠端描述createOffer
:建立一個SDP offer
createAnswer
:建立一個SDP Answer
addStream
: 新增影片流addIceCandidate
:新增對方的網路資訊
監聽
onicecandidate
:監聽到ip資訊onaddstream
:監護獲取對方的影片流資訊onicegatheringstatechange
:監聽網路協商狀態
實現步驟
交換SDP
發起方
peerA
,接收方peerB
都建立一個peer
端發起方獲取本機攝像頭資訊,並透過
addStream
新增影片流到通道接收方獲取本機攝像頭資訊,並透過
addStream
新增影片流到通道發起方透過
createOffer
獲取到SDP
資訊,並透過setLocalDescription
新增到本地影片描述資訊接收方拿到發起方的
SDP
資訊後,透過setRemoteDescription
新增對方SDP
到遠端影片描述資訊,再透過createAnswer
建立應答SDP
資訊,透過setLocalDescription
新增到本地影片描述資訊,再將answer SDP
資訊傳送給發起方發起方拿到對方傳送的
SDP
資訊後,透過setRemoteDescription
新增對方SDP
到遠端影片描述資訊,到這裏,雙方交換SDP
已經完成。
建立Candidate連線
透過監聽onicecandidate
,我們能獲取當前本機的Candidate
(ip,網路資訊),透過信令伺服器傳送給對方,對方接收到後透過addIceCandidate
,將雙方建立連線,從而進行畫面的流式傳遞,通道建立完成。可以監聽icegatheringstatechange
,當iceGatheringState
變為complete
時,表示雙方網路通道已經連線就緒。
簡單實現
peer接收方主要做的事:
初始化攝像頭並透過
addStream
新增建立
offer
設定setLocalDescription
併發送offer sdp
收到
answer sdp
設定setRemoteDescription
收集到
candidate
傳送給對方
// peerA 發起方 let localStream; let localPeer; //獲取本機攝像頭 async function getLocalVideo(){ const stream = await getAndSelectCamera({ audio: true }); if (stream) { localStream = stream; document.getElementById("localVideo").srcObject = stream; connectInit() } } getLocalVideo() // RTC初始化 function connectInit(){ localPeer = new RTCPeerConnection({}); localPeer.addStream(localStream); sendOffer(); localPeer.onaddstream = (e) => { document.getElementById("remoteVideo").srcObject = e.stream; }; localPeer.addEventListener("icegatheringstatechange",(ev) => { if (ev.target.iceGatheringState === "complete") { console.log("影片連線成功") } },false); } // 傳送offer function sendOffer(){ localPeer.createOffer((offer) => { // 傳送Offer給對方 mqttServer.publish("/webrtc",{type: "offer", offer}) // 將offer設定到本地 localPeer.setLocalDescription(offer); }); localPeer.onicecandidate = (event) => { if (event.candidate) { //收集到candidate後,將candidate資訊傳送給對方 mqttServer.publish("/webrtc",{type: "candidate", candidate: event.candidate}) } }; } // 獲取到peerB的answer sdp function getAnswer(data){ localPeer.setRemoteDescription(new RTCSessionDescription(data.answer)) } // 收到對方的candidate後,配對 function handleCandidate(data) { localPeer.addIceCandidate(new RTCIceCandidate(data.candidate)); }
peer接收方主要做的事:
初始化攝像頭並透過
addStream
新增收到對方傳送的
offer
設定setRemoteDescription
建立
answer sdp
設定設定setLocalDescription
併發送answer sdp
收集到
candidate
傳送給對方
// peerB接收方 let localStream; let localPeer; //獲取本機攝像頭 async function getLocalVideo(){ const stream = await getAndSelectCamera({ audio: true }); if (stream) { localStream = stream; document.getElementById("localVideo").srcObject = stream; connectInit() } } getLocalVideo() // RTC初始化 function connectInit(){ localPeer = new RTCPeerConnection({}); localPeer.addStream(localStream); localPeer.onaddstream = (e) => { document.getElementById("remoteVideo").srcObject = e.stream; }; localPeer.onicecandidate = (event) => { if (event.candidate) { mqttServer.publish("/webrtc",{type: "candidate", candidate: event.candidate}) } }; localPeer.addEventListener("icegatheringstatechange",(ev) => { if (ev.target.iceGatheringState === "complete") { console.log("影片連線成功") } },false); } // 收到對方的offer後建立answer function handleSendAnswer(data){ localPeer.setRemoteDescription(new RTCSessionDescription(data.offer)); localPeer.createAnswer( (answer) => { localPeer.setLocalDescription(answer); mqttServer.publish("/webrtc",{type: "answer",answer}) }); } //收到對方的candidate後,配對 function handleCandidate(data) { localPeer.addIceCandidate(new RTCIceCandidate(data.candidate)); }
TURN/STURN
這個主要是進行網路穿透,需要搭建一個TURN
伺服器,在建立RTCPeerConnection
的時候有一個可選引數,配置這個可以進行網路穿透,由於我目前場景沒有使用到這個,所以這裏只是簡單提一下。
{ iceServers: [ { url: "xxxx", username: "xxxx", credential: "xxxxxxxx", }, ], }
最後
到這裏webRTC
音視訊通話已經完成了,其實還是挺有意思的,除此之外,WebRTC
還可以用來實時傳輸檔案等,例如可以實現白板寫字同步功能等