一、什麼是WebRTC
WebRTC(Web Real-Time Communication)是一個由Google、Mozilla、Opera等公司發起的開源專案,它支援網頁瀏覽器進行實時音影片對話。它允許網路應用或者站點,在不借助中間媒介的情況下,建立瀏覽器之間點對點(Peer-to-Peer)的連線,實現影片流和音訊流或者其他任意資料的傳輸。對於開發者而言,WebRTC提供了一套W3C Javascript API,包括音影片的採集、編解碼、網路傳輸、顯示等功能,使得開發者能夠快速構建出音影片應用。WebRTC標準在較高層面上涵蓋了兩種不同的技術:媒體捕獲和點對點連線。
二、媒體捕獲
媒體捕獲裝置包括攝像頭和麥克風,還包括螢幕捕獲裝置。可以利用navigator
這個瀏覽器內建物件身上的API來獲取媒體流。
navigator.mediaDevices.getUserMedia()
:獲取攝像頭和麥克風媒體流navigator.mediaDevices.getDisplayMedia()
:獲取螢幕錄製的媒體流
API的具體使用可以參考 MDN。從技術手冊可以知道,getUserMedia()
方法需要傳入一個constraints
約束物件作為引數。該引數用來指定請求的媒體型別和相對應的引數,而且必須指定至少一個型別。媒體型別具體可以配置哪些屬性,可以使用navigator.mediaDevices.getSupportedConstraints()
來獲取。
2.1 獲取媒體流
// 指定約束物件,獲取音訊流和影片流,並設定影片流的解析度為 1280 x 720
const constraints = {
audio: true,
video: {
with: 1280,
height: 720,
},
};
// 獲取媒體流
const stream = navigator.mediaDevices.getUserMedia(constraints);
console.log(stream);
執行上面的程式碼,瀏覽器會詢問是否開啟攝像頭和麥克風,設定為允許就能獲取媒體流了。
開啟控制檯,發現getUserMedia()
方法返回的是一個Promise。
2.2 展示畫面
將獲取到的媒體流資料,繫結到一個video
標籤的srcObject
屬性上,就能透過video
標籤看到影片畫面了。
這裡有兩個地方需要注意:
getUserMedia()
方法返回的是Promise,所以需要在then方法中去繫結video
標籤的srcObject
屬性,或者使用async await
的方式去獲取Promise的值video
標籤需要設定autoplay
屬性,否則影片沒辦法自動播放
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webrtc</title>
<style>
.play-content {
width: 300px;
height: 300px;
}
</style>
</head>
<body>
<video class="play-content" autoplay></video>
<script src="./webrtc01.js"></script>
</body>
</html>
// 指定約束物件,獲取音訊流和影片流,並設定影片流的解析度為 1280 x 720
const constraints = {
audio: true,
video: {
with: 1280,
height: 720,
},
};
// 獲取媒體流
navigator.mediaDevices.getUserMedia(constraints).then(value => {
// 將媒體流與video標籤繫結
document.querySelector(".play-content").srcObject = value
})
執行上面的程式碼,就能看到影片畫面了
2.3 捕獲螢幕
上面使用了getUserMedia()
這個API來獲取媒體流。接下來使用getDisplayMedia()
這個API來捕獲螢幕。使用方法和getUserMedia()
一致
// 指定約束物件,獲取音訊流和影片流,並設定影片流的解析度為 1280 x 720
const constraints = {
audio: true,
video: {
with: 1280,
height: 720,
},
};
// 獲取螢幕的媒體流,並與video標籤繫結
navigator.mediaDevices.getDisplayMedia(constraints).then((value) => {
document.querySelector(".play-content").srcObject = value;
});
執行上面的程式碼,瀏覽器會讓你選擇一個要開啟的視窗。隨便選擇一個,就能獲取到媒體流了。
三、點對點連線
透過getUserMedia()
和getDisplayMedia()
這兩個API已經可以獲取到媒體流了,那現在就需要考慮如何將媒體流傳送給遠端的瀏覽器。WebRTC要建立點對點的連線,首先有兩個關鍵的問題要解決:
-
媒體協商:
媒體協商主要關注通訊雙方支援的編解碼器及其引數。這包括音訊引數(如取樣率、取樣大小、通道數)和影片引數(如解析度、幀率等)。媒體協商的目的是確保雙方能夠交換和理解彼此的音影片流。這通常透過Session Description Protocol(SDP)進行。
-
網路協商:
網路協商則主要關注在網際網路上建立可通訊的鏈路。由於大多數裝置沒有獨立的公網IP,因此需要進行網路地址轉換(NAT)。WebRTC使用STUN(Session Traversal Utilities for NAT)和TURN(Traversal Using Relays around NAT)協議來幫助完成這一操作。STUN協議用於發現公共IP地址和UDP埠號,而TURN協議則用於在直接通訊不可行時提供中繼服務。網路協商的目的是找到一條可靠的網路路徑,使得通訊雙方能夠建立連線並進行實時通訊。
也就是說,要確保WebRTC成功通訊,需要透過媒體協商來確定雙方都可用的編解碼方式,還需要透過網路協商確定兩端可建立正確通訊鏈路的Ip
地址。下面是WebRTC建立通訊的圖解,針對裡面的過程進行逐一講解
3.1 RTCPeerConnection
具體介紹檢視MDN
RTCPeerConnection是WebRTC(Web實時通訊)API的一部分,它用於在瀏覽器之間建立點對點(peer-to-peer)連線,以支援實時通訊,如音訊、影片和資料交換。RTCPeerConnection提供了建立和管理這些連線所需的底層機制。
根據上面的圖解,可以知道WebRTC建立通訊的流程大致有以下過程:
- 客戶端1建立RTCPeerConnection物件,並將本地媒體流透過addTrack方法新增到物件身上
- 透過createOffer方法建立本地SDP描述,並透過RTCPeerConnection物件的setLocalDescription方法設定本地描述
- 將SDP描述資訊透過信令伺服器轉發給客戶端2
- 客戶端2收到發來的SDP描述,建立RTCPeerConnection物件,並透過
setRemoteDescription
方法設定遠端描述 - 客戶端2透過
createAnswer
方法建立本地的SDP描述,並透過 setLocalDescription方法設定本地描述 - 客戶端2將
Answer SDP
透過信令伺服器轉發給客戶端1 - 客戶端1收到發來的
Answer SDP
,透過setRemoteDescription
方法設定遠端描述
下面根據以上流程,建立一個WebRtcClient
類。該類具有繫結本地媒體流、建立offer、建立answer、接收answer等方法。
在獲取本地流時,我獲取的是螢幕捕獲的流,這樣方便透過兩個不一樣的視窗看到效果。
class WebRtcClient {
// 初始化 RTCPeerConnection 物件
constructor() {
this.pc = new RTCPeerConnection();
this.offer = "";
this.answer = "";
}
// 獲取本地媒體流,並繫結到本地 video標籤
async init() {
const localVideoBox = document.querySelector(".local");
const remoteVideoBox = document.querySelector(".remote");
// 設定約束
const constraints = {
audio: true,
video: true,
};
// 獲取影片流 (切換選擇使用攝像頭還是螢幕)
// const stream = await navigator.mediaDevices.getUserMedia(constraints);
const stream = await navigator.mediaDevices.getDisplayMedia(constraints);
localVideoBox.srcObject = stream;
// 將本地媒體流軌道新增到 RTCPeerConnection物件 中
stream.getTracks().forEach((track) => {
this.pc.addTrack(track, stream);
});
// 監聽遠端流變化
this.pc.ontrack = (e) => {
console.log("遠端流變化了", e);
remoteVideoBox.srcObject = e.streams[0];
};
}
// 建立offer
async createOffer() {
// 存在新的候選,需要更新SDP資訊
this.pc.onicecandidate = async (e) => {
if (e.candidate) {
this.offer = this.pc.localDescription;
}
};
// 建立offer, 並設定為本地SDP描述
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
}
// 建立answer
async createAnswer(offer) {
// 存在新的候選,需要更新SDP資訊
this.pc.onicecandidate = async (e) => {
if (e.candidate) {
this.answer = this.pc.localDescription;
}
};
// 收到對端傳送的offerSDP,設定為遠端SDP
await this.pc.setRemoteDescription(offer);
// 建立answer,設定為本地SDP
const answer = await this.pc.createAnswer();
await this.pc.setLocalDescription(answer);
}
// 新增answerSDP
async addAnswer(answer) {
await this.pc.setRemoteDescription(answer);
}
}
const client = new WebRtcClient();
client.init();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.local,
.remote {
width: 300px;
height: 300px;
}
</style>
</head>
<body>
<video class="local" autoplay></video>
<video class="remote" autoplay></video>
<script src="./webrtcClient.js"></script>
</body>
</html>
下面一步一步操作,實現通訊
-
開啟兩個瀏覽器頁面,分別共享不同的畫面
-
左邊瀏覽器中呼叫client物件的createOffer方法
-
複製左邊client物件的offerSDP,呼叫右邊client物件的createAnswer方法,傳入offerSDP
直接右鍵複製,實際應該使用信令伺服器進行轉發
呼叫右邊client物件的createAnswer方法,傳入offerSDP
-
複製右邊client物件的answerSDP,呼叫左邊client物件的addAnswer方法,並傳入answerSDP
然後,兩個瀏覽器頁面就可以進行視訊通話了
上面的SDP交換過程,是手動進行的,主要是為了瞭解整個通訊流程。後面可以建立一個信令伺服器,透過WebSocket實現SDP的交換。