欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

WebRTC實現(xiàn)雙端音視頻聊天功能(Vue3 + SpringBoot )

 更新時間:2025年05月15日 10:37:06   作者:m0_74823094  
這篇文章主要介紹了WebRTC實現(xiàn)雙端音視頻聊天功能(Vue3 + SpringBoot ),代碼分為前端部分和后端部分,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧

概述

  • 文章描述使用WebRTC技術(shù)實現(xiàn)一對一音視頻通話
  • 由于設(shè)備攝像頭限制(一臺電腦作測試無法在開啟的雙端同時獲取攝像頭數(shù)據(jù)流),導(dǎo)致一臺電腦無法同時測試雙端,因此文章使用mp4音視頻文件模擬攝像頭音視頻數(shù)據(jù)流輸入。
  • 使用技術(shù)
    • 前端:Vue3,WebRTC相關(guān)API,axios
    • 后端信令服務(wù)器實現(xiàn):SpringBoot,WebSocket

相關(guān)概念

  • Peer-to-Peer (P2P) 連接:WebRTC主要是基于 P2P 連接的,這意味著通信是直接在兩端的瀏覽器之間進行的,而不需要經(jīng)過中介服務(wù)器(盡管可能會使用服務(wù)器來初始化和協(xié)調(diào)連接)。這種方式降低了延遲并節(jié)省了帶寬。
  • SDP**(Session Description Protocol)**:**描述媒體信息(如音頻、視頻編碼格式、傳輸協(xié)議等)**的協(xié)議。例如我們在雙方構(gòu)建連接時,我們需要知道對方使用的音視頻編解碼格式,以確保雙方使用相同編解碼格式。編解碼格式就是定義在SDP信息中的其中之一的信息。
  • ICE Candidate:ICE 候選是 WebRTC 在 P2P 連接過程中為尋找最佳傳輸路徑(如 STUN 或 TURN 服務(wù)器)提供的一系列地址和端口。在雙方構(gòu)建連接時需要知道對方的公網(wǎng)IP****地址和端口,以實現(xiàn)P2P連接,Candidate信息中就包含自身的公網(wǎng)IP和端口。
  • STUN(Session Traversal Utilities for NAT**)服務(wù)器**:是 NAT 穿透的協(xié)議,用來獲取客戶端的公網(wǎng) IP 地址和端口。我們身處各種局域網(wǎng)中,對方如果想要和我們構(gòu)建P2P連接,就必然要知道我們的公網(wǎng)IP和端口才能和我們連接上,我們可以通過STUN服務(wù)器獲取我們的公網(wǎng)IP和端口。
  • TURN(Traversal Using Relays around NAT**)服務(wù)器**:當 STUN 連接不可用時,TURN 服務(wù)器作為中繼服務(wù)器轉(zhuǎn)發(fā)數(shù)據(jù)。當STUN服務(wù)器無法幫助我們獲取公網(wǎng)IP和端口時,我們就可以使用TURN服務(wù)器作為中轉(zhuǎn)站傳遞音視頻流數(shù)據(jù)。
  • 信令服務(wù)器:上面介紹了媒體信息SDP和網(wǎng)絡(luò)信息Candidate,這些實際上可以稱為"信令",我們?nèi)绻胍c對端連接,那么我們就需要知道對端的媒體信息和網(wǎng)絡(luò)信息來構(gòu)建連接,信令服務(wù)器就是幫助我們實現(xiàn)兩端的信息交換的。本文中信令服務(wù)器就是我們自己編寫的SpringBoot后端,來幫助兩端互傳連接信息。

雙端連接整體實現(xiàn)步驟概述

在大致知道了上面介紹的WebRTC基本概念之后,我們以雙端音視頻互聯(lián)的整體過程。

假設(shè)存在A端(發(fā)起端)B端(接收端)。

1. 創(chuàng)建RTC連接對象(new RTCPeerConnection),此對象存在構(gòu)建連接時所需的API。

2. A端和B端分別連接后端WebSocket(信令服務(wù)器),以為接下來信息互傳奠定基礎(chǔ)。

3. A端創(chuàng)建媒體信息SDP(createOffer)保存到本地(setLocalDescription),將A端SDP信息通過WebSocket發(fā)送給B端。

4. B端接收到A端的SDP信息,設(shè)置為遠端媒體信息(setRemoteDescription),然后B端創(chuàng)建應(yīng)答媒體信息(實際上就是B端的媒體信息)SDP(createAnswer)保存到本地(setLocalDescription),并將B端創(chuàng)建的應(yīng)答媒體信息SDP通過WebSocket發(fā)送給A端。

5. A端收到B端發(fā)送的應(yīng)答媒體信息SDP后,保存為遠端媒體信息(setRemoteDescription)。

6. 至此,A端和B端媒體信息SDP交換完畢。

7. 開始交換網(wǎng)絡(luò)信息Candidate,我們在創(chuàng)建RTC連接對象時(步驟1)監(jiān)聽網(wǎng)絡(luò)信息的獲?。╫nicecandidate),當我們調(diào)用setRemoteDescription函數(shù)設(shè)置了遠端媒體信息之后,會觸發(fā)onicecandidate并給予condidate網(wǎng)絡(luò)信息。

8. 我們將監(jiān)聽到的網(wǎng)絡(luò)信息candidate通過WebSocket發(fā)送給對端,對端收到后將對方的網(wǎng)絡(luò)信息配置上(addIceCandidate)以實現(xiàn)連接。

9. 當媒體信息SDP和網(wǎng)絡(luò)信息Candidate互相交換并設(shè)置上之后,就可以開始音視頻流數(shù)據(jù)互傳顯示了。

10. 通過addTrack發(fā)送本地流數(shù)據(jù),通過ontrack監(jiān)聽對端音視頻流數(shù)據(jù)的發(fā)送,監(jiān)聽到就顯示對端音視頻。

媒體協(xié)商和網(wǎng)絡(luò)協(xié)商時序圖:

**總結(jié):**在視頻互傳之前重要的就是交換媒體SDP信息和網(wǎng)絡(luò)Candidate信息(媒體和網(wǎng)絡(luò)協(xié)商),當雙方都獲取到對方的媒體和網(wǎng)絡(luò)信息之后。就能夠成功構(gòu)建連接并傳遞音視頻數(shù)據(jù)了。

文章代碼實現(xiàn)注意點

在最開始的概述中有提到,本文提供的1對1音視頻聊天代碼示例中沒有真實調(diào)用用戶攝像頭獲取音視頻流數(shù)據(jù),因為作者只有一臺電腦,為了可以更方便的在一臺電腦上開啟兩端并測試,因此使用了MP4音視頻作為音視頻流數(shù)據(jù)輸入作為測試。

這實際上并不會和真實開啟攝像頭獲取音視頻數(shù)據(jù)流有很大的區(qū)別。僅僅是獲取流數(shù)據(jù)的方式不同罷了。

在真實的場景下,可以使用API:getUserMedia去獲取攝像頭音視頻流數(shù)據(jù)即可。

const stream = await navigator.mediaDevices.getUserMedia({
	video: true,
	audio: true
});

STUN和TURN服務(wù)器的搭建

為了能夠獲取到我們本地的公網(wǎng)IP和端口去和對端創(chuàng)建連接,我們可以嘗試去搭建STUN服務(wù)器和TURN中繼服務(wù)器。

**注:**此步驟不是一定需要做,因為Google給我們提供了一個免費公用的STUN服務(wù)器地址:stun:stun.l.google.com:19302,如果你發(fā)現(xiàn)用不了,或需要搭建復(fù)雜的音視頻通話應(yīng)用,還是推薦自己搭建一下STUN/TURN服務(wù)器。

我們直接搭建開源的Coturn服務(wù)器即可,因為Coturn 同時支持 TURN 和 STUN 協(xié)議。

下面會介紹在CentOS8中搭建Coturn服務(wù)器步驟:

1. 安裝所需依賴包

yum install -y make gcc cc gcc-c++ wget openssl-devel libevent libevent-devel openssl 

2. yum直接一鍵下載安裝

sudo yum install coturn
# (驗證安裝)安裝程序結(jié)束后執(zhí)行如下命令查看是否正確輸出turnserver路徑
which turnserver

3. 配置Coturn相關(guān)屬性,找到配置文件路徑:

find / -name turnserver.conf

4. 獲取服務(wù)器內(nèi)網(wǎng)IP和公網(wǎng)IP

# 輸入命令查看Ip
ifconfig

找到自己啟用的網(wǎng)絡(luò)下的內(nèi)網(wǎng)IP,公網(wǎng)IP就是你連接服務(wù)器的IP地址。

1fcc4a0de8b34d60aef3b3471bbb9efc.png

5. 使用openSSL生成cert和pkey配置的自簽名證書

openssl req -x509 -newkey rsa:2048 -keyout /turn_server_pkey.pem -out /turn_server_cert.pem -days 999 -nodes 

輸入上面命令后,填寫一下證書的一些信息(城市,地區(qū)等),隨便填一下回車回車!就行。

上面的/turn_server_pkey.pem和/turn_server_cert.pem 請自己設(shè)置好保存證書的路徑,上面默認放到了根路徑下。

6. 編輯剛才找到的配置文件

將下面的配置部分修改后替換掉原配置文件的所有內(nèi)容。

# 網(wǎng)卡名
relay-device=eth0
#內(nèi)網(wǎng)IP
listening-ip=172.24.52.189 
listening-port=3478
#內(nèi)網(wǎng)IP,加密訪問配置
relay-ip=172.24.52.189
tls-listening-port=5349
# 外網(wǎng)IP
external-ip=自己的外網(wǎng)IP
relay-threads=500
#打開密碼驗證
lt-cred-mech
cert=/turn_server_cert.pem
pkey=/turn_server_pkey.pem
min-port=40000
max-port=65535
#設(shè)置用戶名和密碼,創(chuàng)建IceServer時使用
user=user:123456
# 外網(wǎng)IP綁定的域名
realm=你自己IP綁定的域名
# 服務(wù)器名稱,用于OAuth認證,默認和realm相同,部分瀏覽器本段不設(shè)可能會引發(fā)cors錯誤。
server-name=你自己IP綁定的域名
# 認證密碼,和前面設(shè)置的密碼保持一致
cli-password=123456

7. 開啟端口訪問

7.1 開啟云服務(wù)器安全組端口

開啟4000-65535端口的原因:外部客戶端與 TURN 服務(wù)器的通信使用動態(tài)端口。通常,操作系統(tǒng)會為每個連接分配一個臨時端口(通常是大于 1024 的端口),而 40000 到 65535 端口 作為 高端端口,是常用的臨時端口范圍。因此,為了確保 TURN 服務(wù)器能夠處理大量的并發(fā)連接,并為每個連接分配一個端口,需要確保 TURN 服務(wù)器的端口范圍足夠大。

7.2開啟本地防火墻端口

#開放端口
firewall-cmd --zone=public --add-port=3478/udp --permanent
firewall-cmd --zone=public --add-port=3478/tcp --permanent
#重啟防火墻
firewall-cmd --reload

8. 啟動Coturn服務(wù)器

turnserver -o -a -f

9. 測試啟動狀態(tài)

訪問測試網(wǎng)站:Trickle ICE

開發(fā)過程描述

如下僅展示關(guān)鍵性代碼解釋說明,具體代碼請到文章最后獲取Gitee源碼地址。

后端開發(fā)流程

  • websocket連接成功后維護用戶連接信息并廣播join消息。數(shù)據(jù)攜帶用戶ID列表。

// 后端維護Session連接的數(shù)據(jù)結(jié)構(gòu)

private final HashMap<String, WebSocketSession> userMap = new HashMap<>();

  • 編寫接收信息通用接口,dto對象包含userID,type,data(JSON序列化字符串),接口根據(jù)傳入userId取出session,給session發(fā)送消息對象。

前端開發(fā)流程

  • 日志系統(tǒng),監(jiān)聽ice狀態(tài)及日志打印。
  • 創(chuàng)建隨機ID,連接ws。
  • 協(xié)商函數(shù):協(xié)商前創(chuàng)建peerConnection對象并監(jiān)聽candidate,當雙方都連接成功后調(diào)用,判斷本地offerFlag狀態(tài),如果為true,創(chuàng)建offer設(shè)置本地并發(fā)送消息給對端。
// STUN 服務(wù)器
const iceServers = [
{
urls: “stun:stun.l.google.com:19302” // Google公開的STUN 服務(wù)器
},
{
urls: “stun:自己的STUN服務(wù)器IP:3478” // 自己的Stun服務(wù)器
},
{
urls: “turn:自己的TRUN服務(wù)器IP:3478”, // 自己的TURN服務(wù)器
username: “userName”,
credential: “Password”
}
];
// 創(chuàng)建RTC連接對象并監(jiān)聽和獲取condidate信息
function createPeerConnection() {
wlog(“開始創(chuàng)建PC對象…”)
peerConnection = new RTCPeerConnection(iceServers);
wlog(“創(chuàng)建PC對象成功”)
// 創(chuàng)建RTC連接對象后連接websocket
initWebSocket();
// 監(jiān)聽網(wǎng)絡(luò)信息(ICE Candidate)
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
candidateInfo = event.candidate;
wlog(“candidate信息變化…”);
// 將candidate信息發(fā)送給遠端
setTimeout(()=>{
sendCandidate(event.candidate);
}, 150)
}
};
// 監(jiān)聽遠端音視頻流
peerConnection.ontrack = (event) => {
nextTick(() => {
wlog(“> 收到遠端數(shù)據(jù)流 <=”)
if (!remoteVideo.value.srcObject) {
remoteVideo.value.srcObject = event.streams[0];
remoteVideo.value.play(); // 強制播放
}
});
// remoteVideo.value.srcObject = event.streams[0];
};
// 監(jiān)聽ice連接狀態(tài)
peerConnection.oniceconnectionstatechange = () => {
wlog(RTC連接狀態(tài)改變:${peerConnection.iceConnectionState});
};
// 添加本地音視頻流到 PeerConnection
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
}
  • candidate監(jiān)聽:當監(jiān)聽到candidate后判斷雙方是否已連接,如果已連接,構(gòu)造并發(fā)送candidate給對端。
    • 解析消息處理器
    • 解析join:type為join取出userId列表,如果為一個代表僅自己在線,標識為創(chuàng)建offer端,日志打印相關(guān)信息,如果有兩個者取出對方ID保存,代表雙方都上線成功,日志打印,調(diào)用協(xié)商函數(shù),開始媒體協(xié)商和網(wǎng)絡(luò)協(xié)商。
    • 解析offer:type為offer,說明收到發(fā)起端offer,將offer設(shè)置為遠端信息,然后創(chuàng)建answer設(shè)置到本地,構(gòu)建answer消息發(fā)送給對端。
    • 解析answer:type為answer,說明收到接收端應(yīng)答,取出answer設(shè)置為遠端消息。
    • 解析candidate:type為candidate,說明收到對端的網(wǎng)絡(luò)信息,取出設(shè)置到本地。
// 消息處理器 - 解析器
function handleSignalingMessage(message) {
wlog(“收到ws消息,開始解析…”)
wlog(message)
let parseMsg = JSON.parse(message);
wlog(解析結(jié)果:${parseMsg});
if (parseMsg.type == “join”) {
joinHandle(parseMsg.data);
} else if (parseMsg.type == “offer”) {
wlog(“收到發(fā)起端offer,開始解析…”);
offerHandle(parseMsg.data);
} else if (parseMsg.type == “answer”) {
wlog(“收到接收端的answer,開始解析…”);
answerHandle(parseMsg.data);
}else if(parseMsg.type == “candidate”){
wlog(“收到遠端candidate,開始解析…”);
candidateHandle(parseMsg.data);
}
}
// 遠端Candidate處理器
async function candidateHandle(candidate){
peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));
wlog(“+++++++ 本端candidate設(shè)置完畢 ++++++++”);
}
// 接收端的answer處理
async function answerHandle(answer) {
wlog(“將answer設(shè)置為遠端信息”);
peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(answer))); // 設(shè)置遠端SDP
}
// 發(fā)起端offer處理器
async function offerHandle(offer) {
wlog(“將發(fā)起端的offer設(shè)置為遠端媒體信息”);
await peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(offer)));
wlog(“創(chuàng)建Answer 并設(shè)置到本地”);
let answer = await peerConnection.createAnswer()
await peerConnection.setLocalDescription(answer);
wlog(“發(fā)送answer給發(fā)起端”);
// 構(gòu)造answer消息發(fā)送給對端
let paramObj = {
userId: oppositeUserId,
type: “answer”,
data: JSON.stringify(answer)
}
// 執(zhí)行發(fā)送
const res = await axios.post(${BaseUrl}/rtcs/sendMessage, paramObj);
}
// 加入處理器
function joinHandle(userIds) {
// 判斷連接的用戶個數(shù)
if (userIds.length == 1 && userIds[0] == userId) {
wlog(“標識為發(fā)起端,等待對方加入房間…”)
isRoomEmpty.value = true;
// 存在一個連接并且是自身,標識我們是發(fā)起端
offerFlag = true;
} else if (userIds.length > 1) {
// 對方加入了
wlog(“對方已連接…”)
isRoomEmpty.value = false;
// 取出對方ID
for (let id of userIds) {
  if (id != userId) {
    oppositeUserId = id;
  }
}
wlog(`對端ID: ${oppositeUserId}`)
// 開始交換SDP和Candidate
swapVideoInfo()
}
}

效果演示

初始狀態(tài)

db67a4f2924c4cc59a68d4b3c69a1ffb.png

發(fā)起端加入房間

cab98851d7524f7caf1156c3b78788a4.png

接收端加入房間

Gitee源碼地址

源碼地址:點擊訪問Gitee項目源代碼。

到此這篇關(guān)于WebRTC實現(xiàn)雙端音視頻聊天(Vue3 + SpringBoot )的文章就介紹到這了,更多相關(guān)WebRTC雙端音視頻聊天內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Linux系統(tǒng)卸載重裝JDK的完整流程

    Linux系統(tǒng)卸載重裝JDK的完整流程

    Linux系統(tǒng)有時候會默認使用OpenJDK版本,需要卸載后重新安裝自己需要的JDK版本,下面這篇文章主要給大家介紹了關(guān)于Linux系統(tǒng)卸載重裝JDK的完整流程,需要的朋友可以參考下
    2024-02-02
  • java如何判斷Date是上午還是下午

    java如何判斷Date是上午還是下午

    文章介紹了三種在Java中判斷Date對象是上午還是下午的方法:使用Calendar類、使用Java 8的java.time包以及使用SimpleDateFormat進行格式化輸出,每種方法都有代碼示例和解釋,幫助開發(fā)者根據(jù)具體需求選擇合適的方法
    2025-03-03
  • springboot中使用@Transactional注解事物不生效的坑

    springboot中使用@Transactional注解事物不生效的坑

    這篇文章主要介紹了springboot中使用@Transactional注解事物不生效的原因,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • java實現(xiàn)文件上傳下載至ftp服務(wù)器

    java實現(xiàn)文件上傳下載至ftp服務(wù)器

    這篇文章主要為大家詳細介紹了java實現(xiàn)文件上傳下載至ftp服務(wù)器的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • 劍指Offer之Java算法習(xí)題精講數(shù)組與列表的查找及字符串轉(zhuǎn)換

    劍指Offer之Java算法習(xí)題精講數(shù)組與列表的查找及字符串轉(zhuǎn)換

    跟著思路走,之后從簡單題入手,反復(fù)去看,做過之后可能會忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會發(fā)現(xiàn)質(zhì)的變化
    2022-03-03
  • Maven基礎(chǔ)知識大梳理

    Maven基礎(chǔ)知識大梳理

    這篇文章主要是Maven基礎(chǔ)知識大梳理,Maven主要是用來解決導(dǎo)入java類依賴的jar,編譯java項目主要問題,大家可以讀一讀這篇文章,更深一步的了解Maven
    2021-08-08
  • Spring事務(wù)的開啟原理詳解

    Spring事務(wù)的開啟原理詳解

    這篇文章主要介紹了Spring事務(wù)的簡單實現(xiàn)步驟,幫助大家更好的理解和學(xué)習(xí)使用spring,感興趣的朋友可以了解下
    2021-03-03
  • springBoot Junit測試用例出現(xiàn)@Autowired不生效的解決

    springBoot Junit測試用例出現(xiàn)@Autowired不生效的解決

    這篇文章主要介紹了springBoot Junit測試用例出現(xiàn)@Autowired不生效的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • SpringBoot實現(xiàn)EMQ設(shè)備的上下線告警

    SpringBoot實現(xiàn)EMQ設(shè)備的上下線告警

    EMQX?的上下線系統(tǒng)消息通知功能在客戶端連接成功或者客戶端斷開連接,需要實現(xiàn)設(shè)備的上下線狀態(tài)監(jiān)控,所以本文給大家介紹了如何通過SpringBoot實現(xiàn)EMQ設(shè)備的上下線告警,文中有詳細的代碼示例,需要的朋友可以參考下
    2023-10-10
  • Python中scrapy框架的ltem和scrapy.Request詳解

    Python中scrapy框架的ltem和scrapy.Request詳解

    這篇文章主要介紹了Python中scrapy框架的ltem和scrapy.Request詳解,Item是保存爬取數(shù)據(jù)的容器,它的使用方法和字典類似,不過,相比字典,Item提供了額外的保護機制,可以避免拼寫錯誤或者定義字段錯誤,需要的朋友可以參考下
    2023-09-09

最新評論