国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

WebRTC的學(xué)習(xí)(java版本信令服務(wù))

這篇具有很好參考價(jià)值的文章主要介紹了WebRTC的學(xué)習(xí)(java版本信令服務(wù))。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

WebRTC的學(xué)習(xí)

1. 相關(guān)地址

1.1 文檔教學(xué)

? WebRTC中文網(wǎng):http://webrtc.p2hp.com/#google_vignette

? WebRTC中文社區(qū):https://webrtc.org.cn/

? WebRTC英文官網(wǎng):https://webrtc.org/

? WebRTC安全相關(guān):http://webrtc-security.github.io/

? coturn開(kāi)源地址:https://github.com/coturn/coturn

? stun、trun測(cè)試網(wǎng)站:https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

? NAT知識(shí):

? ?? P2P知識(shí):https://zhuanlan.zhihu.com/p/488135992

? ?? P2P技術(shù)詳解(一):NAT詳解——詳細(xì)原理、P2P簡(jiǎn)介https://www.cnblogs.com/mlgjb/p/8243646.htm

? ?? P2P技術(shù)詳解(二):P2P中的NAT穿越(打洞)方案詳解 https://www.jianshu.com/p/9bfbcbee0abb

? ?? P2P技術(shù)詳解(三):P2P技術(shù)之STUN、TURN、ICE詳解 https://www.jianshu.com/p/258e7d8be2ba

? ?? 詳解P2P技術(shù)中的NAT穿透原理 https://www.jianshu.com/p/f71707892eb2

? WebRTC 代碼相關(guān)博客:

? ?? https://www.bbsmax.com/A/B0zqLrWNdv/

? ?? https://www.an.rustfisher.com/webrtc/web-samples/getUserMedia-open-camera/

? ?? https://github.com/shushushv/webrtc-p2p

?? WebRTC原理簡(jiǎn)述:https://www.jianshu.com/p/476f39de86ed

?? WebRTC介紹及簡(jiǎn)單使用:https://zhuanlan.zhihu.com/p/490239698

? 后續(xù)補(bǔ)充…

? 注意:

? - 如果只是初期入門只需要看第一個(gè)中文官網(wǎng)就行了,如果追求最新的文檔去英文官網(wǎng)。
? - 我這里的案例實(shí)現(xiàn)代碼時(shí)使用了大佬的代碼(代碼相關(guān)博客里的第一個(gè)博客)。
? - 代碼實(shí)現(xiàn)時(shí)強(qiáng)力推薦去看其他相關(guān)博客,寫(xiě)的很不錯(cuò)。

?

1.2 視頻教學(xué)

? b站學(xué)習(xí)地址:https://www.bilibili.com/video/BV1D14y1W7qp?p=1&vd_source=d6cd8b3f892acbf22f02da2bfa7d95fe

我本人也是看該視頻進(jìn)行入門的,非常感謝該UP和講師,講的挺好的,缺點(diǎn)是視頻不夠清晰。

?

2. 簡(jiǎn)述

2.1 什么是WebRTC?

? WebRTC (Web Real-Time Communications) 是一項(xiàng)實(shí)時(shí)通訊技術(shù),它允許網(wǎng)絡(luò)應(yīng)用或者站點(diǎn),在不借助中間媒介的情況下,建立瀏覽器之間點(diǎn)對(duì)點(diǎn)(Peer-to-Peer)的連接,實(shí)現(xiàn)視頻流和(或)音頻流或者其他任意數(shù)據(jù)的傳輸。WebRTC 包含的這些標(biāo)準(zhǔn)使用戶在無(wú)需安裝任何插件或者第三方的軟件的情況下,創(chuàng)建點(diǎn)對(duì)點(diǎn)(Peer-to-Peer)的數(shù)據(jù)分享和電話會(huì)議成為可能。

? 簡(jiǎn)單來(lái)說(shuō),就是可以實(shí)現(xiàn)例如微信電話(實(shí)時(shí)通訊)的技術(shù),并且它不需要任何第三方插件的和軟件的限制,在瀏覽器里就可以實(shí)現(xiàn)視頻通話。

?

2.2 WebRtc可以做什么?

? WebRTC 有許多不同的用例,從使用攝像頭或麥克風(fēng)的基本 Web 應(yīng)用程序到更高級(jí)的視頻通話應(yīng)用程序和屏幕共享。我們收集了許多代碼示例,以更好地說(shuō)明該技術(shù)的工作原理以及您可以使用它的用途.

? WebRTC提供了視頻會(huì)議的核心技術(shù),包括音視頻的采集、編解碼、網(wǎng)絡(luò)傳輸、展示等功能,并且還支持跨平臺(tái),包括linux、windows、mac、android等。

? 這句話是WebRTC中文網(wǎng)內(nèi)的,里面還有很多小例子,示例。

? 我建議先把通信原理看了之后再去寫(xiě)例子。

?

2.3 WebRTC的架構(gòu)

java webrtc,webrtc,spring boot,websocket

?

2.3.1 WebRTC架構(gòu)組件介紹

Your Web App

? Web開(kāi)發(fā)者開(kāi)發(fā)的程序,Web開(kāi)發(fā)者可以基于集成WebRTC的瀏覽器提供的web API開(kāi)發(fā)基于視頻、音頻的實(shí)時(shí)通信 應(yīng)用。

Web API

? 面向第三方開(kāi)發(fā)者的WebRTC標(biāo)準(zhǔn)API(Javascript),使開(kāi)發(fā)者能夠容易地開(kāi)發(fā)出類似于網(wǎng)絡(luò)視頻聊天的web應(yīng)用, 最新的標(biāo)準(zhǔn)化進(jìn)程可以查看這里。

?

WebRTC Native C++ API

? 本地C++ API層,使瀏覽器廠商容易實(shí)現(xiàn)WebRTC標(biāo)準(zhǔn)的Web API,抽象地對(duì)數(shù)字信號(hào)過(guò)程進(jìn)行處理。

?

Transport / Session

? 傳輸/會(huì)話層

? 會(huì)話層組件采用了libjingle庫(kù)的部分組件實(shí)現(xiàn),無(wú)須使用xmpp/jingle協(xié)議 。

?

VoiceEngine

? 音頻引擎是包含一系列音頻多媒體處理的框架。

? PS:VoiceEngine是WebRTC極具價(jià)值的技術(shù)之一,是Google收購(gòu)GIPS公司后開(kāi)源的。在VoIP上,技術(shù)業(yè)界領(lǐng)先。

? Opus:支持從6 kbit/s到510 kbit/s的恒定和可變比特率編碼,幀大小從2.5 ms到60 ms,各種采樣率從8 kHz(4 kHz 帶寬)到48 kHz(20 kHz帶寬,可復(fù)制人類聽(tīng)覺(jué)系統(tǒng)的整個(gè)聽(tīng)力范圍)。由IETF RFC 6176定義。

? NetEQ 模塊是Webrtc語(yǔ)音引擎中的核心模塊 ,一種動(dòng)態(tài)抖動(dòng)緩沖和錯(cuò)誤隱藏算法,用于隱藏網(wǎng)絡(luò)抖動(dòng)和數(shù)據(jù)包丟失 的負(fù)面影響。保持盡可能低的延遲,同時(shí)保持最高的語(yǔ)音質(zhì)量。

?

VideoEngine

? WebRTC視頻處理引擎

? VideoEngine是包含一系列視頻處理的整體框架,從攝像頭采集視頻到視頻信息網(wǎng)絡(luò)傳輸再到視頻顯示整個(gè)完整過(guò)程 的解決方案。

? VP8 視頻圖像編解碼器,是WebRTC視頻引擎的默認(rèn)的編解碼器 。

? VP8適合實(shí)時(shí)通信應(yīng)用場(chǎng)景,因?yàn)樗饕轻槍?duì)低延時(shí)而設(shè)計(jì)的編解碼器。

?

2.4 WebRTC的原理

? 首先思考的問(wèn)題:兩個(gè)不同網(wǎng)絡(luò)環(huán)境的(具備攝像頭/麥克風(fēng)多媒體設(shè)備的)瀏覽器,要實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn) 的實(shí)時(shí)音視頻對(duì) 話,難點(diǎn)在哪里?

? 1. 了解對(duì)方的媒體格式、支持的最大分辨率和其他媒體信息?
? 2. 要了解彼此的網(wǎng)絡(luò),就有可能找到一條通信鏈路?
? 3. 兩個(gè)終端還沒(méi)有建立連接時(shí),如何交換“媒體信息”和“網(wǎng)絡(luò)信息”呢?

?

2.4.1 媒體協(xié)商(sdp)

java webrtc,webrtc,spring boot,websocket

? 彼此要了解對(duì)方支持的媒體格式?

? 為了保證兩端都有正確的編碼和解碼,最簡(jiǎn)單的方法就是取它們的交集H264

? 注意:有一種特殊的協(xié)議叫做 Session Description protocol (SDP) ,可以用來(lái)描述上述信息。

? 媒體協(xié)商:在webrtc中,參與視頻通信的雙方必須首先交換SDP信息,這樣雙方才能知根知底,而交換SDP的過(guò)程,也稱為"媒體協(xié)商"。

?

2.4.2 網(wǎng)絡(luò)協(xié)商(candidate)

? 同樣,在復(fù)雜的網(wǎng)絡(luò)環(huán)境中,要在兩端之間建立連接,必須有一個(gè)雙方都可以訪問(wèn)的鏈路。

? 理想的網(wǎng)絡(luò)情況是每個(gè)瀏覽器的電腦都是私有公網(wǎng)IP,可以直接進(jìn)行點(diǎn)對(duì)點(diǎn)連接。

java webrtc,webrtc,spring boot,websocket
?

? 實(shí)際情況是我們的電腦和電腦之前或大或小都是在某個(gè)局域網(wǎng)中,需要NAT(Network Address Translation,網(wǎng)絡(luò)地址轉(zhuǎn)換)。

java webrtc,webrtc,spring boot,websocket

? 注意:如果是在同一個(gè)局域網(wǎng)中,那么直接使用相同的內(nèi)網(wǎng)網(wǎng)段就可以了,但是一般情況下都是不同的。

? 在中國(guó)的網(wǎng)絡(luò)環(huán)境下,據(jù)統(tǒng)計(jì),至少有一半的網(wǎng)絡(luò)不能直接連接。我個(gè)人認(rèn)為根本原因是:在互聯(lián)網(wǎng)發(fā)展的早期,絕大多數(shù)IP4地址資源都被國(guó)外所占據(jù)。當(dāng)輪到中國(guó)等發(fā)展中國(guó)家使用IP地址時(shí),大多數(shù)計(jì)算機(jī)沒(méi)有公網(wǎng)IP地址,只能通過(guò)路由器和交換機(jī)進(jìn)行NAT轉(zhuǎn)換,相當(dāng)一部分NAT是對(duì)稱的?;旧?,沒(méi)有辦法播放它。

? 那么我們就需要使用NAT進(jìn)行轉(zhuǎn)換。
java webrtc,webrtc,spring boot,websocket
?

? 所以在解決 WebRTC 使用過(guò)程中的上述問(wèn)題的時(shí)候,我們需要用到 STUNTURN 。

?

2.4.2.1 STUN

? STUN(Session Traversal Utilities for NAT,NAT會(huì)話穿越應(yīng)用程序)是一種網(wǎng)絡(luò)協(xié)議,它允許位于NAT(或多重 NAT)后的客戶端找出自己的公網(wǎng)地址,查出自己位于哪種類型的NAT之后以及NAT為某一個(gè)本地端口所綁定的 Internet端端口。這些信息被用來(lái)在兩個(gè)同時(shí)處于NAT路由器之后的主機(jī)之間創(chuàng)建UDP通信。該協(xié)議由 RFC 5389 定 義。

? 在遇到上述情況的時(shí)候,我們可以建立一個(gè)STUN服務(wù)器,這個(gè)服務(wù)器做什么用的呢?主要是給無(wú)法在公網(wǎng)環(huán)境下的 視頻通話設(shè)備分配公網(wǎng)IP用的。這樣兩臺(tái)電腦就可以在公網(wǎng)IP中進(jìn)行通話。

java webrtc,webrtc,spring boot,websocket

? 使用一句話說(shuō)明 STUN 做的事情就是:告訴我你的 公網(wǎng)IP地址+端口 是什么?

? 搭建STUN服務(wù)器很簡(jiǎn)單,媒體流傳輸是按照 P2P 的方式。

? 那么問(wèn)題來(lái)了, STUN 并不是每次都能成功的為需要 NAT 的通話設(shè)備分配 IP 地址的,P2P 在傳輸媒體流時(shí),使用的本地帶寬,在多人視頻通話的過(guò)程中,通話質(zhì)量的好壞往往需要根據(jù)使用者本地的帶寬確定。

? 那么怎么辦? TURN 可以 很好的解決這個(gè)問(wèn)題。

?

2.4.2.2 TURN

? TURN 的全稱為 Traversal Using Relays around NAT ,是 STUN/RFC5389 的一個(gè)拓展,主要添加了 Relay 功能。如果終端在 NAT 之后, 那么在特定的情景下,有可能使得終端無(wú)法和其對(duì)等端(peer)進(jìn)行直接的通信,這時(shí)就需要公網(wǎng) 的服務(wù)器作為一個(gè) 中繼 , 對(duì)來(lái)往的數(shù)據(jù)進(jìn)行轉(zhuǎn)發(fā)。

? 這個(gè)轉(zhuǎn)發(fā)的協(xié)議就被定義為TURN。

java webrtc,webrtc,spring boot,websocket

? 在 STUN 分配公網(wǎng)IP 失敗 后,可以通過(guò) TURN服務(wù)器 請(qǐng)求公網(wǎng)IP地址作為中繼地址。

? 這種方式的帶寬由服務(wù)器端承擔(dān),在多人視頻聊天的時(shí)候,本地帶寬壓力較小,并且根據(jù) Google 的說(shuō)明,TURN協(xié)議 可以使用在所有的環(huán)境中。

?

2.4.2.3 總結(jié)

? (單向數(shù)據(jù)200kbps 一對(duì)一通話)

? 以上是 WebRTC 中經(jīng)常用到的2個(gè)協(xié)議,STUNTURN 服務(wù)器我們使用 coturn開(kāi)源項(xiàng)目 來(lái)搭建。

? 補(bǔ)充:ICESTUNTURN 不一樣,ICE不是一種協(xié)議,而是一個(gè)框架(Framework),它整合了STUNTURN 。

? coturn開(kāi)源項(xiàng)目 集成了 STUNTURN的功能。

? 在WebRTC中用來(lái)描述網(wǎng)絡(luò)信息的術(shù)語(yǔ)叫 candidate 。

? - 媒體協(xié)商 sdp

? - 網(wǎng)絡(luò)協(xié)商 candidate

?

2.4.2 媒體協(xié)商+網(wǎng)絡(luò)協(xié)商數(shù)據(jù)的交換通道

? 從上面 [2.4.1 媒體協(xié)商(sdp)](#2.4.1 媒體協(xié)商(sdp)) 和 [2.4.2 網(wǎng)絡(luò)協(xié)商(candidate)](2.4.2 網(wǎng)絡(luò)協(xié)商(candidate)) ,我們知道了2個(gè)客戶端,那怎么去交換?是不是需要一個(gè)中間商去做交換?

? 所以 我們需要一個(gè) 信令服務(wù)器Signal server )轉(zhuǎn)發(fā)彼此的媒體信息和網(wǎng)絡(luò)信息。

java webrtc,webrtc,spring boot,websocket

? 如上圖,我們?cè)诨?WebRTC API 開(kāi)發(fā)應(yīng)用(APP)時(shí),可以將彼此的APP連接到信令服務(wù)器(

? Signal Server ,一般 搭建在公網(wǎng),或者兩端都可以訪問(wèn)到的局域網(wǎng)),借助信令服務(wù)器,就可以實(shí)現(xiàn)上面提到的 SDP 媒體信息及 Candidate 網(wǎng)絡(luò)信息交換。

? 我簡(jiǎn)單理解為如下:

? 你給朋友寫(xiě)信,信寫(xiě)好了需要寄給朋友,把信放到信箱由郵局進(jìn)行派送,郵遞員交到朋友手上,但是每個(gè)地方這么大,不可能是一個(gè)郵遞員送,肯定是由當(dāng)?shù)氐泥]局進(jìn)行分發(fā)派送。

? 那么,在這個(gè)過(guò)程中郵局(郵遞員)充當(dāng)了信令服務(wù)器(Signal Server)的作用,你寫(xiě)的信就是sdp,Candidate 就是你填寫(xiě)的朋友地址,這個(gè)地址可能因?yàn)楹艽?,?dǎo)致需要根據(jù)地址(NAT/Relay)進(jìn)行郵局分發(fā),例如市郵局 -> 縣郵局 -> 鄉(xiāng)鎮(zhèn)郵局 ->郵遞員。

? NAT: 你主動(dòng)根據(jù)郵局地址去拿。

? Relay:讓郵遞員送上門。

?

? 交換SDP的過(guò)程:

java webrtc,webrtc,spring boot,websocket
?

? 1. Amy(假設(shè)一個(gè)人的名字)通過(guò)setLocalDescription方法保存自己的SDP信息,然后通過(guò)offer方法發(fā)送給信令服務(wù)器。

? 2. 信息服務(wù)器將Amy的SDP轉(zhuǎn)發(fā)給另一端的Bob(另一個(gè)虛構(gòu)的名字),Bob將首先調(diào)用setremotedescription來(lái)保存Amy的SDP。
? 3. 然后Bob調(diào)用setLocalDescription方法來(lái)保存他的SDP,然后使用answer方法通過(guò)信令服務(wù)器將他的SDP發(fā)送給Amy。

? 4. Amy收到Bob的SDP后,調(diào)用setRemoteDescription進(jìn)行保存,雙方完成SDP交換,找到交集。如果他們能達(dá)成協(xié)議,他們就可以建立一個(gè)p2p連接并開(kāi)始通信。

?

2.5 WebRTC如何查看APIs?

? 英文網(wǎng)址:[媒體設(shè)備入門 | WebRTC中文網(wǎng) (p2hp.com)]

? 如果看英文吃力,可以選擇如下地址學(xué)習(xí)部分示例:

? WebRTC打開(kāi)本地?cái)z像頭 - RustFisher 安卓|Java|設(shè)計(jì)模式|WebRTC|Python|NestJS|PyQt

?

3. 安裝Coturn穿透和轉(zhuǎn)發(fā)服務(wù)器

3.1 安裝依賴
ubuntu系統(tǒng)
sudo apt-get install libssl-dev

sudo apt-get install libevent-dev

?

centos系統(tǒng)
sudo yum install openssl-devel

sudo yum install libevent-devel

?

3.2 下載源碼進(jìn)行編譯安裝Coturn
# 本次所需完整執(zhí)行代碼
git clone https://github.com/coturn/coturn
cd coturn
./configure
make
sudo make install

?

進(jìn)行 ./configure 的時(shí)候報(bào)錯(cuò),如下圖:

java webrtc,webrtc,spring boot,websocket

?

查閱后表示需要安裝 g++

sudo apt-get install g++

java webrtc,webrtc,spring boot,websocket

?

缺少 pkg-config

sudo apt-get install pkg-config

?

再次進(jìn)行:./configure

java webrtc,webrtc,spring boot,websocket

?

make

java webrtc,webrtc,spring boot,websocket
?

sudo make install

3.3 啟動(dòng)并測(cè)試
啟動(dòng)
# 啟動(dòng)
sudo nohup turnserver -L 0.0.0.0 -a -u lqf:123456 -v -f -r nort.gov &

?

測(cè)試

? 測(cè)試網(wǎng)站:Trickle ICE (webrtc.github.io)
?
? 測(cè)試stun:

java webrtc,webrtc,spring boot,websocket

? 注意:stun不需要username和password,我這里是多寫(xiě)了。

?

測(cè)試turn:

java webrtc,webrtc,spring boot,websocket

? 注意:測(cè)試trun需要username和password,且 IceTransports value 選擇為relay。

? 我這里不知道為什么是701,查了一些資料也沒(méi)有頭緒,因?yàn)樵品?wù)器過(guò)期了,就使用的本地虛擬機(jī),所以不知道是不是因?yàn)檫@個(gè),到時(shí)候再試試。
?

4. 案例實(shí)現(xiàn)

4.1 一對(duì)一通話

4.1.1 一對(duì)一通話場(chǎng)景

java webrtc,webrtc,spring boot,websocket

? 在一對(duì)一通話場(chǎng)景中,每個(gè) Peer 均創(chuàng)建有一個(gè) PeerConnection 對(duì)象,由一方主動(dòng)發(fā) Offer SDP,另一方則應(yīng)答 AnswerSDP,最后雙方交換 ICE Candidate 從而完成通話鏈路的建立。但是在中國(guó)的網(wǎng)絡(luò)環(huán)境中,據(jù)一些統(tǒng)計(jì)數(shù)據(jù)顯示,至少1半的網(wǎng)絡(luò)是無(wú)法直接穿透打通,這種情況下只能借助 TURN 服務(wù)器中轉(zhuǎn)。
?

4.1.2 一對(duì)一通話場(chǎng)景代碼實(shí)現(xiàn)
技術(shù)選型

? ? 前端 -> html、css、js、WebRTC、websocket

? ? 后端 -> springboot + websocket

?

整體執(zhí)行流程

java webrtc,webrtc,spring boot,websocket

?

頁(yè)面代碼
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>WebRTC + WebSocket</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<style>
    html,
    body {
        margin: 0;
        padding: 0;
    }

    #main {
        position: absolute;
        width: 370px;
        height: 550px;
    }

    #localVideo {
        position: absolute;
        background: #757474;
        top: 10px;
        right: 10px;
        width: 100px;
        height: 150px;
        z-index: 2;
    }

    #remoteVideo {
        position: absolute;
        top: 0px;
        left: 0px;
        width: 100%;
        height: 100%;
        background: #222;
    }

    #buttons {
        z-index: 3;
        bottom: 20px;
        left: 90px;
        position: absolute;
    }

    #toUser {
        border: 1px solid #ccc;
        padding: 7px 0px;
        border-radius: 5px;
        padding-left: 5px;
        margin-bottom: 5px;
    }

    #toUser:focus {
        border-color: #66afe9;
        outline: 0;
        -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
        box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
    }

    #call {
        width: 70px;
        height: 35px;
        background-color: #00BB00;
        border: none;
        margin-right: 25px;
        color: white;
        border-radius: 5px;
    }

    #hangup {
        width: 70px;
        height: 35px;
        background-color: #FF5151;
        border: none;
        color: white;
        border-radius: 5px;
    }
</style>
</head>

<body>
    <div id="main">
        <video id="remoteVideo" playsinline autoplay></video>
        <video id="localVideo" playsinline autoplay muted></video>

        <div id="buttons">
            <span id="myName" style="color: red;"></span>
            <input id="toUser" placeholder="輸入在線好友賬號(hào)" /><br />
            <button id="call">視頻通話</button>
            <button id="hangup">掛斷</button>
        </div>
    </div>
</body>
<!-- 可引可不引 -->
<!--<script th:src="@{/js/adapter-2021.js}"></script>-->
<script type="text/javascript" th:inline="javascript">

    // 生成一個(gè)隨機(jī)的用戶名
    let username = '' + Math.floor(Math.random() * (100 - 1) + 1);
    if(username){
        document.getElementById("myName").innerText = username
    }
    let localVideo = document.getElementById('localVideo');
    let remoteVideo = document.getElementById('remoteVideo');
    let websocket = null;
    let peer = null;

    WebSocketInit();
    ButtonFunInit();

    /* WebSocket */
    function WebSocketInit() {
        //判斷當(dāng)前瀏覽器是否支持WebSocket
        if ('WebSocket' in window) {
            // 使用192.168.8.57的目的是打包成app的時(shí)候可以通過(guò)局域網(wǎng)訪問(wèn)
            url = "ws://192.168.8.57:8080/webrtc/" + username
            // url = "ws://127.0.0.1:8080/webrtc/" + username
            websocket = new WebSocket(url);
        } else {
            alert("當(dāng)前瀏覽器不支持WebSocket!");
        }

        //連接發(fā)生錯(cuò)誤的回調(diào)方法
        websocket.onerror = function (e) {
            console.log(e)
            alert("WebSocket連接發(fā)生錯(cuò)誤!");
        };

        //連接關(guān)閉的回調(diào)方法
        websocket.onclose = function () {
            console.error("WebSocket連接關(guān)閉");
        };

        //連接成功建立的回調(diào)方法
        websocket.onopen = function () {
            console.log("WebSocket連接成功");
        };

        //接收到消息的回調(diào)方法
        websocket.onmessage = async function (event) {
            let { type, fromUser, msg, sdp, iceCandidate } = JSON.parse(event.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r"));

            console.log(type);

            if (type === 'hangup') {
                console.log(msg);
                document.getElementById('hangup').click();
                return;
            }

            if (type === 'call_start') {
                // msg = 0 表示拒絕,1表示同意
                let msg = "0"
                if (confirm(fromUser + "發(fā)起視頻通話,確定接聽(tīng)嗎") == true) {
                    document.getElementById('toUser').value = fromUser;
                    WebRTCInit();
                    msg = "1"
                }

                websocket.send(JSON.stringify({
                    type: "call_back",
                    toUser: fromUser,
                    fromUser: username,
                    msg: msg
                }));

                return;
            }

            if (type === 'call_back') {
                if (msg === "1") {
                    console.log(document.getElementById('toUser').value + "同意視頻通話");

                    //創(chuàng)建本地視頻并發(fā)送offer
                    let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
                    localVideo.srcObject = stream;
                    stream.getTracks().forEach(track => {
                        peer.addTrack(track, stream);
                    });

                    let offer = await peer.createOffer();
                    await peer.setLocalDescription(offer);

                    let newOffer = offer.toJSON();
                    newOffer["fromUser"] = username;
                    newOffer["toUser"] = document.getElementById('toUser').value;
                    websocket.send(JSON.stringify(newOffer));
                } else if (msg === "0") {
                    alert(document.getElementById('toUser').value + "拒絕視頻通話");
                    document.getElementById('hangup').click();
                } else {
                    alert(msg);
                    document.getElementById('hangup').click();
                }

                return;
            }

            if (type === 'offer') {
                let stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
                localVideo.srcObject = stream;
                stream.getTracks().forEach(track => {
                    peer.addTrack(track, stream);
                });

                await peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
                let answer = await peer.createAnswer();
                let newAnswer = answer.toJSON();

                newAnswer["fromUser"] = username;
                newAnswer["toUser"] = document.getElementById('toUser').value;
                websocket.send(JSON.stringify(newAnswer));

                await peer.setLocalDescription(answer);
                return;
            }

            if (type === 'answer') {
                peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
                return;
            }

            if (type === '_ice') {
                peer.addIceCandidate(iceCandidate);
                return;
            }

        }
    }

    /* WebRTC */
    function WebRTCInit() {

        // RTCPeerConnection 的配置,內(nèi)網(wǎng)時(shí)不需要開(kāi)啟stun、turn
        const defaultConfiguration = {
            bundlePolicy: "max-bundle",
            rtcpMuxPolicy: "require",
            iceTransportPolicy: "all", // relay:如果使用了turn建議使用relay
            // ice
            iceServers: [
                {
                    "urls": [
                        "turn:192.168.147.122:3478?transport=udp",
                        "turn:192.168.147.122:3478?transport=tcp"
                    ],
                    "username": 'lqf',
                    "credential": "123456"
                },
                {
                    "urls": [
                        "stun:192.168.147.122:3478"
                    ]
                }
            ]
        }
        var Rtc = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
        if(!Rtc){
            alert("不支持WebRTC")
        }
        // peer = new RTCPeerConnection(defaultConfiguration);
        peer = new RTCPeerConnection();
        //ice
        peer.onicecandidate = function (e) {
            if (e.candidate) {
                websocket.send(JSON.stringify({
                    type: '_ice',
                    toUser: document.getElementById('toUser').value,
                    fromUser: username,
                    iceCandidate: e.candidate
                }));
            }
        };

        //track
        peer.ontrack = function (e) {
            if (e && e.streams) {
                remoteVideo.srcObject = e.streams[0];
            }
        };
    }

    /* 按鈕事件 */
    function ButtonFunInit() {
        //視頻通話
        document.getElementById('call').onclick = function (e) {
            document.getElementById('toUser').style.visibility = 'hidden';

            let toUser = document.getElementById('toUser').value;
            if (!toUser) {
                alert("請(qǐng)先指定好友賬號(hào),再發(fā)起視頻通話!");
                return;
            }

            if (peer == null) {
                WebRTCInit();
            }

            websocket.send(JSON.stringify({
                type: "call_start",
                fromUser: username,
                toUser: toUser,
            }));
        }

        //掛斷
        document.getElementById('hangup').onclick = function (e) {
            document.getElementById('toUser').style.visibility = 'unset';

            if (localVideo.srcObject) {
                const videoTracks = localVideo.srcObject.getVideoTracks();
                videoTracks.forEach(videoTrack => {
                    videoTrack.stop();
                    localVideo.srcObject.removeTrack(videoTrack);
                });
            }

            if (remoteVideo.srcObject) {
                const videoTracks = remoteVideo.srcObject.getVideoTracks();
                videoTracks.forEach(videoTrack => {
                    videoTrack.stop();
                    remoteVideo.srcObject.removeTrack(videoTrack);
                });

                //掛斷同時(shí),通知對(duì)方
                websocket.send(JSON.stringify({
                    type: "hangup",
                    fromUser: username,
                    toUser: document.getElementById('toUser').value,
                }));
            }

            if (peer) {
                peer.ontrack = null;
                peer.onremovetrack = null;
                peer.onremovestream = null;
                peer.onicecandidate = null;
                peer.oniceconnectionstatechange = null;
                peer.onsignalingstatechange = null;
                peer.onicegatheringstatechange = null;
                peer.onnegotiationneeded = null;

                peer.close();
                peer = null;
            }

            localVideo.srcObject = null;
            remoteVideo.srcObject = null;
        }
    }
</script>

</html>

?

后端代碼

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.blacktea</groupId>
    <artifactId>wbrtc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>webrtc</name>
    <description>springboot整合webrtc的demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 引入WebSocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

?

配置類

package com.blacktea.webrtc.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import java.text.SimpleDateFormat;

/**
 * @description:
 * @author: black tea
 * @date: 2023/3/15 19:37
 */
@Configuration
// 掃描cn.hutool.extra.spring包下所有類并注冊(cè)之
@ComponentScan(basePackages={"cn.hutool.extra.spring"})
public class MyWebSocketConfig{

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }

    @Bean
    public ObjectMapper mapper(){
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return mapper;
    }
}

?

WebSocket服務(wù)類

package com.blacktea.webrtc.server;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @description: WebRtc的 WebSocket 服務(wù)
 * @author: black tea
 * @date: 2023/3/15 18:30
 */
@Slf4j
@Component
@ServerEndpoint(value = "/webrtc/{username}")
@Import(cn.hutool.extra.spring.SpringUtil.class)
public class WebRtcWebSocketServer {

    /**
     * 連接集合
     */
    private static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 連接建立成功時(shí)的調(diào)用方法
     * @param session 會(huì)話對(duì)象
     * @param username 用戶
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        log.info("ws client 連接成功,username={}, session={}", username, session);
        sessionMap.put(username, session);
    }

    @OnClose
    public void onClose(Session session){
        Set<Map.Entry<String, Session>> entries = sessionMap.entrySet();
        for(Map.Entry<String, Session> entry : entries){
            if (entry.getValue() == session){
                String username = entry.getKey();
                log.info("ws client 關(guān)閉成功,username={}, session={}", username, session);
                sessionMap.remove(username);
                break;
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error){
        log.error("ws 出現(xiàn)異常,", error);
    }

    /**
     * 服務(wù)器接收到客戶端消息時(shí)調(diào)用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try{
            log.info("receive message:{}", message);
            ObjectMapper mapper = SpringUtil.getBean(ObjectMapper.class);
            //JSON字符串轉(zhuǎn) HashMap
            HashMap hashMap = mapper.readValue(message, HashMap.class);

            //消息類型
            String type = (String) hashMap.get("type");

            //to user
            String toUser = (String) hashMap.get("toUser");
            Session toUserSession = sessionMap.get(toUser);
            String fromUser = (String) hashMap.get("fromUser");

            //msg
            String msg = (String) hashMap.get("msg");

            //sdp
            String sdp = (String) hashMap.get("sdp");

            //ice
            Map iceCandidate  = (Map) hashMap.get("iceCandidate");

            HashMap<String, Object> map = new HashMap<>();
            map.put("type",type);

            //呼叫的用戶不在線
            if(toUserSession == null){
                toUserSession = session;
                map.put("type","call_back");
                map.put("fromUser","系統(tǒng)消息");
                map.put("msg","Sorry,呼叫的用戶不在線!");

                send(toUserSession,mapper.writeValueAsString(map));
                return;
            }

            //對(duì)方掛斷
            if ("hangup".equals(type)) {
                map.put("fromUser",fromUser);
                map.put("msg","對(duì)方掛斷!");
            }

            //視頻通話請(qǐng)求
            if ("call_start".equals(type)) {
                map.put("fromUser",fromUser);
                map.put("msg","1");
            }

            //視頻通話請(qǐng)求回應(yīng)
            if ("call_back".equals(type)) {
                map.put("fromUser",toUser);
                map.put("msg",msg);
            }

            //offer
            if ("offer".equals(type)) {
                map.put("fromUser",toUser);
                map.put("sdp",sdp);
            }

            //answer
            if ("answer".equals(type)) {
                map.put("fromUser",toUser);
                map.put("sdp",sdp);
            }

            //ice
            if ("_ice".equals(type)) {
                map.put("fromUser",toUser);
                map.put("iceCandidate",iceCandidate);
            }

            send(toUserSession,mapper.writeValueAsString(map));
        }catch(Exception e){
            log.error("onMessage,異常:", e);
        }
    }

    /**
     * 封裝一個(gè)send方法,發(fā)送消息到前端
     */
    private void send(Session session, String message) {
        try {
            log.info("send message:{}",message);
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("message,待發(fā)送的數(shù)據(jù):{},異常:", message, e);
        }
    }

}

? 注意:因?yàn)槲椰F(xiàn)在沒(méi)有公網(wǎng)服務(wù)器,所以現(xiàn)在我的所有測(cè)試都只進(jìn)行了內(nèi)網(wǎng)測(cè)試。

?

4.1.3 效果圖

4.13.1 瀏覽器效果圖

java webrtc,webrtc,spring boot,websocket

?

4.13.2 安卓效果圖

電腦瀏覽器效果圖

java webrtc,webrtc,spring boot,websocket

?

手機(jī)瀏覽器效果圖

java webrtc,webrtc,spring boot,websocket

? app的話可以使用 Hbuilder X 進(jìn)行打包。

? 注意:打包的時(shí)候記得修改自己的websocket服務(wù)的ip和端口。

?

5. 公網(wǎng)部署

因?yàn)楝F(xiàn)在沒(méi)有公網(wǎng)服務(wù)器,所以暫時(shí)不弄。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-538913.html

到了這里,關(guān)于WebRTC的學(xué)習(xí)(java版本信令服務(wù))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Qt6.5.1+WebRTC學(xué)習(xí)筆記(十二)環(huán)境搭建流媒體服務(wù)器(ubuntu22.04+SRS)

    Qt6.5.1+WebRTC學(xué)習(xí)筆記(十二)環(huán)境搭建流媒體服務(wù)器(ubuntu22.04+SRS)

    若只是實(shí)現(xiàn)一對(duì)一通信,僅使用webrtc就足夠了。但有時(shí)間需要進(jìn)行多個(gè)人的直播會(huì)議,當(dāng)人比較多時(shí),建議使用一個(gè)流媒體服務(wù)器,筆者使用的是SRS。 這個(gè)開(kāi)源項(xiàng)目資料比較全,筆者僅在此記錄下搭建過(guò)程 1.操作系統(tǒng)ubuntu22.04?64位 x86架構(gòu)(建議更新到最新) 2.安裝編譯器相

    2024年02月09日
    瀏覽(101)
  • WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務(wù)搭建

    WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務(wù)搭建

    iOS開(kāi)發(fā)-ossrs服務(wù)WebRTC本地視頻通話服務(wù)搭建 之前開(kāi)發(fā)中使用到了ossrs,這里記錄一下ossrs支持的WebRTC本地服務(wù)搭建。 ossrs是什么呢? SRS(Simple Realtime Server)是一個(gè)簡(jiǎn)單高效的實(shí)時(shí)視頻服務(wù)器,支持RTMP、WebRTC、HLS、HTTP-FLV、SRT等多種實(shí)時(shí)流媒體協(xié)議。 官網(wǎng)地址:https://ossrs.net/lt

    2024年02月12日
    瀏覽(22)
  • 搭建WebRTC服務(wù)器

    1、AppRTC 房間服務(wù),代碼:github.com/webrtc/appr… 2、Collider 信令服務(wù),AppRTC源碼里自帶 3、CoTurn 打洞服務(wù),代碼:github.com/coturn/cotu… 打包資源: 鏈接: pan.baidu.com/s/1ulx1FVRN… 提取碼: 3e5f 1、操作系統(tǒng): Centos 64bits 2、Google webrtc的服務(wù)器Demo:詳見(jiàn)https://github.com/webrtc/apprtc 3、域名: ap

    2024年02月13日
    瀏覽(20)
  • WebRTC學(xué)習(xí)

    WebRTC(Web Real-Time Communications)是一項(xiàng)實(shí)時(shí)通訊技術(shù),它允許網(wǎng)絡(luò)應(yīng)用或者站點(diǎn),在不借助中間媒介的情況下,建立瀏覽器之間點(diǎn)對(duì)點(diǎn)(Peer-to-Peer)的連接,實(shí)現(xiàn)視頻流和(或)音頻流或者其他任意數(shù)據(jù)的傳輸。WebRTC 包含的這些標(biāo)準(zhǔn)使用戶在無(wú)需安裝任何插件或者第三方的軟

    2024年02月02日
    瀏覽(14)
  • 【webrtc】nack學(xué)習(xí)筆記

    本文大量參考了以下大神的文章,表示感謝: WebRTC 中的Nack WebRTC之視頻NackModule WebRtc Video Receiver(三)-NACK丟包重傳原理 WebRTC QoS | NACK 格式與發(fā)送策略 webrtc QOS筆記四 Nack機(jī)制淺析 WebRTC中的Audio Nack nackmoudle后面被nackmodule2 替代 WebRtc Video Receiver(三)-NACK丟包重傳原理 對(duì)m79的nackmoud

    2024年02月12日
    瀏覽(16)
  • webrtc學(xué)習(xí)(七)-媒體協(xié)商

    webrtc學(xué)習(xí)(七)-媒體協(xié)商

    ? ? ? ?媒體協(xié)商嘴主要的作用就是看通信雙方都支持那些編解碼器,這些編解碼器又包含那些參數(shù),比如音頻的參數(shù)包括采樣率,采樣大小,通道數(shù),對(duì)于視頻的參數(shù)包括分辨率幀率等一系列參數(shù),此外傳輸中用的payloadtype也是協(xié)商中確定的,這些信息都是在sdp中進(jìn)行的描述

    2024年02月11日
    瀏覽(9)
  • 構(gòu)建WebRTC技術(shù)需要的后端服務(wù)

    構(gòu)建WebRTC技術(shù)需要的后端服務(wù)

    ??歡迎點(diǎn)贊 :?? 收藏 ?留言 ?? 如有錯(cuò)誤敬請(qǐng)指正,賜人玫瑰,手留余香! ??本文作者:由webmote 原創(chuàng) ??作者格言:新的征程,我們面對(duì)的不是技術(shù)而是人心,人心不可測(cè),海水不可量,唯有技術(shù),才是深沉黑夜中的一座閃爍的燈塔 ! 當(dāng)下直播界最炙手可熱的技術(shù),

    2024年02月15日
    瀏覽(21)
  • 用于WebRTC的coturn服務(wù)器的搭建

    用于WebRTC的coturn服務(wù)器的搭建

    前言: ? ? ? ? 自己在研究WebRTC相關(guān)內(nèi)容,在遠(yuǎn)端連接老是繞不開(kāi)搭建ICE服務(wù)器,搜索了無(wú)數(shù)文章和方法,還是久久無(wú)進(jìn)展,最后頭皮嗯嘛了才成功,故想總結(jié)一下我的搭建步驟,供大家參考,避免少走彎路!??! 1、前期準(zhǔn)備 準(zhǔn)備一個(gè)自己的公網(wǎng)服務(wù)器,可以是阿里云或其

    2024年01月24日
    瀏覽(23)
  • 互動(dòng)直播之WebRTC服務(wù)器Kurento實(shí)戰(zhàn)

    互動(dòng)直播之WebRTC服務(wù)器Kurento實(shí)戰(zhàn)

    1. Kurento Kurento的主要組件是 Kurento媒體服務(wù)器 (KMS),負(fù)責(zé)媒體傳輸,處理,記錄和播放。KMS建立在出色的GStreamer多媒體庫(kù)之上,并提供以下功能: 網(wǎng)絡(luò)流協(xié)議,包括HTTP,RTP和WebRTC。 支持媒體混合和媒體路由/調(diào)度的組通信(MCU和SFU功能)。 對(duì)實(shí)現(xiàn)計(jì)算機(jī)視覺(jué)和增強(qiáng)現(xiàn)實(shí)算

    2024年02月21日
    瀏覽(19)
  • WebRTC+Kamamilio+rtpengine+Websocket SIP服務(wù)器搭建教程

    WebRTC+Kamamilio+rtpengine+Websocket SIP服務(wù)器搭建教程

    發(fā)現(xiàn)國(guó)內(nèi)使用SIP的資料真的很少,由于公司需要使用語(yǔ)音視頻電話,在網(wǎng)上找了一大圈也沒(méi)找到相關(guān)案例,最后自己折騰出一個(gè)方案,故此分享出來(lái)。 開(kāi)始之前不得不介紹一下這五個(gè)東西。 Kamamilio 是 OpenSER 的前身,是C語(yǔ)言寫(xiě)的一個(gè)Sip服務(wù),支持在 在Linux/UNIX系統(tǒng)上運(yùn)行,優(yōu)

    2024年02月15日
    瀏覽(24)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包