【翻譯】第三章 - [Dan_Ristic]webrtc開發互動實踐

摘要

介於英語考試在即,當前工做須要,特地找了這本書,原書《Learning_WebRTC_Develop_interactive》,以前很久都沒有中文版,執意決定自行翻譯。後來在豆瓣閱讀找到了翻譯版,有須要者能夠移步購買Learning WebRTC中文版
說實話,WebRTC沒有網絡基礎是不太容易消化理解,這本書的好處是重實踐,在實踐成果中慢慢理解深奧的計算機網絡。2017年著做,其中一些api相繼廢棄,本人在原書pdf中作了修改批註,有須要的聯繫641007257@qq.com
第三章是一個核心篇章,因此決定今後開始翻譯,前面兩章是基礎,比較簡單,後續會補上翻譯文,大概計劃與本年末翻譯完整本。javascript

能力不高,技術有限,文章僅供參考,勿噴!!!css

建立一個基礎應用

理解udp實時傳輸

​ webrtc應用首選udp傳輸的緣由是,它提供了高性能傳輸,不用像tcp那樣控制數據的準確性。目前,大多數web應用都創建在tcp協議之上,緣由在於它爲用戶提供保證,其中一些特性以下:html

  • 發送的全部數據都會接收到確認
  • 數據一旦發送失敗,將中止繼續發送並從新發送失敗的數據
  • 確保數據惟一性

​ 今天,大多數的web應用選擇tcp,也正式基於這些特性。假如,你發送一個html頁面,讓全部數據以正確的順序排列並確保它到達另外一方是有意義的。可是,這一技術不否和全部的用戶場景。例如,在多人遊戲中傳輸數據。視頻遊戲中的大多數數據在幾秒鐘甚至更短期內變得陳舊,這就意味着用戶只關心過去幾秒內發生的事情,僅此而已。若是須要保證每一條數據都能到達另外一方,那麼當數據丟失時,這可能會致使很大的瓶頸。java

clipboard.png

​ 由於tcp約束的緣由,使得webrtc的開發人員首選udp做爲他們的傳輸方式。webrtc的音頻和視頻並不須要的可靠的鏈接,而是,須要高性能傳輸。咱們能夠接收丟包,從而,也意味着,對於這類的應用程序來講,udp是一個更好的選擇。web

​ UDP經過作出大量的不保證來實現這種狀況,它被構建成一個不太可靠的傳輸層,對您發送的數據進行較少的假設。upd不可靠傳輸的緣由以下:算法

  • 不肯定數據發送給對方的順序
  • 傳輸過程當中可能丟包,不能確保每一個數據包都能準確無誤的發送給對方
  • 不跟蹤數據包的狀態,即時客戶端丟失數據,也不會中止發送數據

如今,webrtc能夠以儘快的方式發送音頻和視頻,這也透漏出webrtc是一個多麼複雜的主題。並非每一個網絡都容許udp流量經過,具備企業防火牆的大型網絡能夠直接阻止UDP流量,以防止惡意鏈接。這些鏈接必須沿着與今天大多數網頁下載不一樣的路徑傳播。必須圍繞UDP構建許多變通方法和流程,以使其適用於普遍的受衆。在WebRTC技術方面,這只是冰山一角。在接下來的幾節中,咱們將介紹在瀏覽器中啓用WebRTC的其餘支持技術。json

webrtc接口

接下來的幾節將介紹目前在瀏覽器中實現的WebRTC API。這些函數和對象容許開發人員與WebRTC層進行通訊,並與其餘用戶創建對等鏈接。它由幾個主要技術組成:api

  • The RTCPeerConnection object
  • Signaling and negotiation
  • Session Description Protocol (SDP)
  • Interactive Connectivity Establishment (ICE)

The RTCPeerConnection object

RTCPeerConnection是webrtc api的主入口點。提供初始化鏈接、對等鏈接、賦值媒體信息。它處理與另外一個用戶的UDP鏈接的建立。如今咱們開始熟悉這個api,由於在本書的其他部分將會屢次出現。瀏覽器

RTCPeerConnection的主要做用是在瀏覽器中維護對等鏈接的會話和狀態。它還處理對等鏈接的設置和建立。它封裝了全部這些內容,並公開了一組在鏈接過程當中的關鍵點被觸發的事件。經過這些事件,您能夠訪問對等鏈接期間發生的配置和內部信息:安全

clipboard.png

RTCPeerConnection對象是瀏覽器中的一個簡單對象,可使用新的構造函數進行實例化,以下所示:

var myConnection = new RTCPeerConnection(configuration);
myConnection.onaddstream = function (stream) {
 // Use stream here
};

鏈接接受配置對象,咱們將在本章後面介紹。在示例中,咱們還爲onaddstream事件添加了一個處理程序。
當遠程用戶將視頻或音頻流添加到其對等鏈接時,會觸發此操做。咱們還將在本章後面介紹這一點。

信令和協商

​ 一般,鏈接到另外一個瀏覽器須要知道瀏覽器在網絡上的位置。一般狀況下是採用ip地址和端口號的方式尋找目的主機。你的計算機或移動設備的IP地址容許其餘啓用Internet的設備直接在彼此之間發送數據; 這就是RTCPeerConnection的基礎。一旦這些設備知道如何在互聯網上找到彼此,他們還須要知道如何相互交談。這意味着交換有關每一個設備支持的協議以及音視頻編解碼器等有關數據。

​ 這也意味着,爲了鏈接到另外一個用戶,你須要更多的瞭解他們。一種可能的解決方案是在你的計算機上存儲能夠鏈接到的用戶的列表。要啓用與其餘用戶的通訊,您只需交換聯繫信息,讓WebRTC處理其他的信息。然而,這有一個弊端,你必須和你須要鏈接的用戶手動共享信息。你必須維護一個您想要鏈接的任何用戶的大列表,並經過其餘通訊渠道交換信息。使用WebRTC,咱們可使這一過程自動化。

​ 幸運的是,咱們今天使用的大多數Web通訊應用程序中解決了這個問題。要與Facebook或LinkedIn等熱門服務上的任何人聯繫,您只須要知道他們的名字並搜索他們。而後,你能夠將他們添加到已知聯繫人列表中,並隨時訪問其信息。

​ 這一過程就是WebRTC中的信令和協商。

信令過程包括幾個步驟:

  1. 生成對等鏈接的潛在候選列表。
  2. 用戶或算法將選擇用戶進行鏈接。
  3. 信令層將通知該用戶有人想要與他/她聯繫,而且他/她能夠接受或拒絕。
  4. 通知第一個用戶接收要約鏈接。
  5. 若是接受,第一個用戶將與另外一個用戶啓動RTCPeerConnection
  6. 用戶都將經過信令信道交換有關其計算機的硬件和軟件信息。
  7. 兩個用戶還將經過信令信道交換關於他們的計算機的位置信息。
  8. 用戶之間的鏈接成功或失敗。

​ 然而,這只是WebRTC信令可能發生的一個例子。實際上,WebRTC規範沒有包含關於兩個用戶如何交換信息的任何標準。這是因爲不斷增長的鏈接用戶標準列表。今天存在許多標準,甚至在信號和談判過程當中創造了更多標準。WebRTC標準做者決定,試圖就一個標準達成一致意見將阻止它向前發展。

​ 這本書中,咱們將創建本身的信令和協商履行條約。編寫一個能夠在兩個瀏覽器之間傳輸信息的簡單服務器。雖然它很簡單而且容易出現安全漏洞,但它應該讓你很好地理解這個過程在WebRTC中是如何工做的。同時,你也能夠探究其餘公司提供的信令方案。有數百種信令和談判解決方案,天天都有更多的信息和協商解決方案。有些集成了當前的電話或基於聊天的實現,例如XMPPSIP,有些還提出了一種全新的信令方式。

會話描述協議

​ 要與其餘用戶創建聯繫,您須要先了解一下這些用戶。關於另外一個客戶端的一些最重要的事情是他們支持的音頻和視頻編解碼器,他們的網絡帶寬,以及他們的計算機能夠處理多少數據,並且還須要在客戶之間輕鬆傳輸。因爲咱們沒有指定如何傳輸這些數據,所以它也應該可以經過多種類型的傳輸協議進行傳輸。這意味着咱們須要一個基於字符串的名片,其中包含咱們能夠發送給其餘用戶的全部用戶信息。
SDP正好爲咱們提供了這樣的功能。

SDP的偉大之處在於它已經存在了很長時間,能夠追溯到90年代末的第一次初稿。這意味着SDP是一種在客戶端之間創建基於媒體的鏈接的可靠方法。在WebRTC以前,它已被用於許多其餘類型的應用程序,例如電話和基於文本的聊天。這就意味着在實現和應用方面有不少好的資源。SDP是瀏覽器提供的基於字符串的blob數據。這個字符串格式是一組由換行符分割的鍵值對:

<key>=<value>\n

鍵是一個單一字符,用於創建這個類型的值。該值是一組結構化文本,包含機器可讀配置值。而後經過換行符分割不一樣的鍵值對。SDP將涵蓋給定用戶的描述,時序配置和媒體約束。在與用戶創建鏈接的過程當中,RTCPeerConnection對象給出了SDP。當咱們在本章後面開始使用RTCPeerConnection對象時,您能夠輕鬆地將其打印到JavaScript控制檯。這將容許您準確查看SDP中包含的內容,以下所示:

v=0
o=- 1167826560034916900 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1
m=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:Vl5FBUBecw/U3EzQ
a=ice-pwd:OtsNG6FzUH8uhNEhOg9/hprb
a=ice-options:google-ice
a=fingerprint:sha-256 
FB:56:7D:B6:E0:C7:E7:39:FE:47:5A:12:6C:B4:4E:0E:2D:18:CE:AE:33:92: 
A9:60:3F:14:E4:D9:AA:0D:BE:0D
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendrecv
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 
inline:zE+3pkUbJyFG4UmmvPxG/OFC4+QE24X8Zf3iOSCf
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
a=ssrc:4274470304 cname:+j4Ma6UfMsCcQCWK
a=ssrc:4274470304 msid:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1 
a1751f6b-98de-469b-b6c0-81f46e19009d
a=ssrc:4274470304 mslabel:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1
a=ssrc:4274470304 label:a1751f6b-98de-469b-b6c0-81f46e19009d
m=video 1 RTP/SAVPF 100 116 117
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:Vl5FBUBecw/U3EzQ
a=ice-pwd:OtsNG6FzUH8uhNEhOg9/hprb
a=ice-options:google-ice
a=fingerprint:sha-256 FB:56:7D:B6:E0:C7:E7:39:FE:47:5A:12:6C:B4:4E:0E:
2D:18:CE:AE:33:92: 
A9:60:3F:14:E4:D9:AA:0D:BE:0D
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-sendtime
a=sendrecv
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 
inline:zE+3pkUbJyFG4UmmvPxG/OFC4+QE24X8Zf3iOSCf
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=ssrc:3285139021 cname:+j4Ma6UfMsCcQCWK
a=ssrc:3285139021 msid:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1 
bd02b355-b8af-4b68-b82d-7b9cd03461cf
a=ssrc:3285139021 mslabel:K44HTOZVjyAyAlvUVD3pOLu8i0LdytHiWRp1
a=ssrc:3285139021 label:bd02b355-b8af-4b68-b82d-7b9cd03461cf

這是在會話啓動過程當中從我本身的機器上獲取的。如您所見,生成的代碼第一眼看上去很複雜。它首先肯定IP地址的鏈接。而後,設置有關請求的基本信息,例如我是否請求音頻、視頻仍是二者。接下來,它會設置一些音頻信息,包括加密類型和ICE配置等主題。它還以相同的方式設置視頻信息。

最後,咱們的目標不是理解每一行代碼,而是理解SDP的用途。在本書的學習過程當中,您永遠沒必要直接使用它,但可能須要在未來的某個時候使用它。總之,SDP扮演一個業務名片,供其餘用戶與您創建鏈接的時候使用。SDP與信令和協商相結合,是對等鏈接的前半部分。在接下來的幾節中,咱們將介紹在兩個用戶知道如何找到對方以後會發生什麼。

參考
P2P通訊標準協議(三)之ICE

尋找另外一個用戶的明確線路

今天大多數網絡的很大一部分是安全性。您正在使用的任何網絡都有幾層訪問控制,告訴您的數據在何處以及如何發送。這意味着鏈接到另外一個用戶須要找到一條清晰的路徑,不只僅是您本身的網絡,還有其餘用戶的網絡。WebRTC內部涉及多種技術:

  • NAT會話遍歷(STUN)
  • NAT中繼設備遍歷(TURN)
  • 交互式鏈接創建(ICE)

這些涉及許多服務器和鏈接,以便WebRTC正確使用。要了解它們的工做原理,咱們首先應該可視化典型WebRTC鏈接過程的佈局:

clipboard.png
首先是找出你的IP地址。全部鏈接到Internet的設備都有一個IP地址,用於標識它們在Web上的位置。這是將數據包定向到正確目的地的方法。在局域網內查找IP地址時會出現問題。路由器隱藏計算機的IP地址並將其替換爲另外一個,以提升安全性並容許多臺計算機使用相同的網絡地址。一般,您能夠在本身,網絡路由器和公共Internet之間擁有多個IP地址。

NAT會話遍歷

STUN是在兩個對等體之間找到良好鏈接的第一步。它有助於識別Internet上的每一個用戶,而且旨在由其餘協議用於創建對等鏈接。它首先向服務器發出請求,使用STUN協議啓用。而後,服務器識別發出請求的客戶端的IP地址,並將其返回給客戶端。而後,客戶端可使用給定的IP地址標識本身。

​ 使用STUN協議須要啓用STUN的服務器才能鏈接。目前,在Firefox和Chrome中,默認服務器直接由瀏覽器供應商提供。這很是適合快速啓動並運行測試。

參考
P2P通訊標準協議(一)之STUN

NAT中繼設備遍歷

在某些狀況下,防火牆可能限制性太強,不容許任何基於STUN的流量到其餘用戶。在企業NAT中可能就是這種狀況,它利用端口隨機化來容許比一般發現的數千個設備更多的設備。在這種狀況下,咱們須要一種與另外一個用戶鏈接的不一樣方法。 這就是TURN

​ 這種方式是在客戶端之間添加一箇中繼,表明客戶端充當對等鏈接。而後,客戶端從TURN服務器獲取其信息,就像經過向服務器發出請求從流行的視頻服務流式傳輸視頻同樣。這要求TURN服務器下載,處理和重定向每一個客戶端發送給它的每一個數據包。這就是爲何使用TURN在製做WebRTC鏈接時一般被認爲是最後的手段,由於設置高質量的TURN服務的成本很高。

​ 關於STUN與TURN的使用有不少不一樣的統計數據,但它們彷佛都指向了相同的結論 - 大部分時間,沒有TURN你的用戶就會沒事。將WebRTC與STUN一塊兒使用將適用於大多數網絡配置。在設置您本身的WebRTC服務時,最好跟蹤此信息並自行決定使用TURN服務的成本是否值得。

參考
P2P通訊標準協議(二)之TURN

交互式鏈接創建

​ 如今咱們已經介紹了STUN和TURN,咱們能夠經過另外一個名爲ICE的標準來了解它是如何組合在一塊兒的。利用STUN和TURN爲對等鏈接提供成功路由的過程。它的工做原理是找到每一個用戶可用的一系列地址,並按排序順序測試每一個地址,直到找到適合兩個客戶端的組合。

​ ICE的過程從不對每一個用戶的網絡配置作出假設開始。它將逐步經過一系列步驟來發現每一個客戶端的網絡是如何設置的。此過程將使用不一樣的技術集來執行此操做。目標是發現有關每一個網絡的足夠信息,以創建成功的鏈接。

​ 經過使用STUN和TURN找到每一個ICE候選者。若是鏈接失敗,它將查詢STUN服務器以查找外部IP地址並將TURN服務器的位置附加爲備份。每當瀏覽器找到新候選者時,它通知客戶端應用程序它須要經過信令信道發送ICE候選者。在找到並測試了足夠的地址並創建鏈接後,該過程終於結束了。

參考
P2P通訊標準協議(三)之ICE

構建一個基本的WebRTC應用程序

​ 如今咱們已經很好地理解了WebRTC的使用方式,咱們將構建咱們的第一個支持WebRTC的應用程序。到本章結束時,您將擁有一個可用的WebRTC網頁,您能夠在其中查看實際使用的技術。咱們將把咱們剛剛介紹的全部信息提取到一個易於開發的示例中。咱們將涵蓋:

  • 建立RTCPeerConnection
  • 建立SDP服務和響應
  • 尋找ICE對等候選人
  • 建立一個成功的WebRTC鏈接

建立RTCPeerConnection

​ 不幸的是,咱們如今建立的不是一個徹底的對等應用程序,攝像頭只能捕獲到本身。咱們在本章中的目的是將瀏覽器窗口鏈接到自身,從用戶的攝像頭流式傳輸視頻數據。最終目標是在頁面上得到兩個視頻流,一個直接來自攝像頭,另外一個來自瀏覽器在本地製做的WebRTC鏈接。

​ 雖然這並不徹底有用,但它可使代碼更具可讀性和便於理解,從而幫助咱們。咱們將在稍後學習如何使用服務器進行遠程鏈接。因爲咱們鏈接的環境是咱們本地的瀏覽器,所以咱們沒必要擔憂網絡不穩定或建立服務器。完成項目後,您的應用程序應以下所示:

clipboard.png

正如你所看到的,除了我英俊的面孔,這個例子是很是基本的。首先,咱們將採用與第2章「獲取用戶媒體」中建立的第一個示例相似的步驟。您須要建立另外一個HTML頁面並使用本地Web服務器託管它。在第2章獲取用戶媒體中參考「獲取媒體設備訪問權限」部分中的「設置靜態服務器」小節多是一個好主意,並查看如何設置開發環境。

​ 咱們將採起的第一步是建立一些處理多個瀏覽器支持的函數。這些將可以告訴咱們當前的瀏覽器是否支持咱們須要使用的功能來使咱們的應用程序工做。它還將規範化API,確保咱們始終可使用相同的功能,不管咱們運行什麼瀏覽器。

​ 要開始使用,請使用JavaScript源文件設置新網頁。咱們的HTML頁面上將包含兩個視頻元素,一個用於第一個客戶端,另外一個用於第二個客戶端:

<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8" />
 <title>Learning WebRTC - Chapter 4: Creating a 
 RTCPeerConnection</title>
 </head>
 <body>
 <div id="container">
 <video id="yours" autoplay></video>
 <video id="theirs" autoplay></video>
 </div>
 <script src="main.js"></script>
 </body>
</html>

若是您常常作HTML5網頁,那麼此頁面的html和head標籤應該很熟悉。這是任何符合HTML5的頁面的標準格式。
有不少不一樣的樣板模板用於建立頁面,而這個模板是我以爲最簡單的,同時還能完成工做。只要視頻元素存在,就沒有什麼會完全改變咱們的應用程序的工做方式,因此若是你須要對這個文件進行更改,請隨意這樣作。

您會注意到兩個標記爲您和他們的視頻元素。這些將是咱們的兩個視頻源,將模擬鏈接到另外一個同伴。在本章的其他部分中,您將被視爲啓動鏈接的本地用戶。
其餘用戶 - 他們的 - 將被視爲咱們正在進行WebRTC鏈接的遠程用戶,即便他們並不是物理上位於其餘地方。

​ 最後,包括咱們的腳本功能。請始終牢記在HTML頁面的末尾添加此內容。這能夠保證正文中的元素可使用,而且頁面已徹底加載,以便JavaScript與之交互。
接下來,咱們將建立咱們的JavaScript源代碼。建立一個名爲main.js的新文件,並使用如下代碼開始填寫它:

function hasUserMedia() {
 navigator.getUserMedia = navigator.getUserMedia || 
 navigator.webkitGetUserMedia || navigator.mozGetUserMedia || 
 navigator.msGetUserMedia;
 return !!navigator.getUserMedia;
}
function hasRTCPeerConnection() {
 window.RTCPeerConnection = window.RTCPeerConnection || 
 window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
 return !!window.RTCPeerConnection;
}

​ 如今咱們能夠告訴用戶支持哪些API,讓咱們繼續使用它們。接下來的幾個步驟也應該很是熟悉。咱們將重複第2章「獲取用戶媒體」中的「約束媒體流」部分中遇到的一些功能,以獲取用戶的相機流。在咱們對WebRTC作任何事情以前,咱們應該從用戶那裏獲取本地攝像頭流。這能夠確保用戶準備好建立對等鏈接,而且在創建對等鏈接以前,咱們沒必要等待用戶接受攝像頭共享。

咱們在WebRTC中構建的大多數應用程序都將經歷一系列狀態。讓WebRTC工做最困難的部分是按正確的順序作事。若是一個步驟發生在另外一個步驟以前,它能夠快速分解應用程序。這些狀態是阻塞的,這意味着咱們不能在沒有完成前一個狀態的狀況下進入下一個狀態。如下是咱們的應用程序如何工做的概述:

clipboard.png

首先,咱們須要獲取用戶媒體。確保用戶接受視頻和音頻的媒體流正常。

​ 接下來,咱們建立對等鏈接。在斷開鏈接以前啓動進程。這是咱們可使用咱們想要使用的ICE服務器配置WebRTC鏈接的地方。此時,瀏覽器閒置着等待鏈接過程開始。

​ 當其中一個用戶建立offer時,應用就正式開始了。這使瀏覽器開始行動,並開始準備與另外一個用戶創建對等鏈接。提議和響應是本章討論的信令過程的一部分。

​ 同時,瀏覽器還將尋找其餘對等方能夠鏈接的候選端口和IP組合。它將在一段時間內繼續執行此操做,直到能夠創建鏈接或鏈接失敗。完成此操做後,WebRTC鏈接過程結束,兩個用戶能夠開始共享信息。

​ 下一段代碼將捕獲用戶的攝像頭並使其在咱們的流變量中可用。您如今能夠在JavaScript中的兩個函數定義以後添加如下代碼:

var yourVideo = document.querySelector("#yours"),
    theirVideo = document.querySelector("#theirs"),
    yourConnection, theirConnection;
if (hasUserMedia()) {
    navigator.getUserMedia({ video: true, audio: false }, function
        (stream) {
        yourVideo.srcObject = stream;
        if (hasRTCPeerConnection()) {
            startPeerConnection(stream);
        } else {
            alert("Sorry, your browser does not support WebRTC.");
        }
    }, function (error) {
        alert("Sorry, we failed to capture your camera, please try again.");
    });
} else {
    alert("Sorry, your browser does not support WebRTC.");
}

​ 第一部分從文檔中選擇咱們的視頻元素,並設置一些咱們將在接下來使用的變量。咱們假設瀏覽器此時支持querySelector API。而後,咱們檢查用戶是否能夠訪問getUserMedia API。若是他們不這樣作,咱們的程序將停在此處,並提醒用戶他們不支持WebRTC

​ 若是成功,咱們會嘗試從用戶那裏獲取相機。這是一種異步操做,由於用戶必須贊成共享他們的相機。若是成功,咱們將本地視頻的流設置爲用戶的流,以便他們能夠成功地看到這一點。若是失敗,咱們會通知用戶錯誤並中止該過程。

​ 最後,咱們檢查用戶是否能夠訪問RTCPeerConnection API。若是是這樣,咱們調用將啓動鏈接過程的函數(這將在下一節中定義)。若是沒有,咱們停在這裏並再次通知用戶。

​ 下一步是實現上一節中調用的startPeerConnection函數。此函數將建立咱們的RTCPeerConnection對象,設置SDP offer和響應,並找到兩個對等體的ICE候選對象。
​ 如今咱們爲兩個對等體建立RTCPeerConnection對象。將如下內容添加到JavaScript文件中:

function startPeerConnection(stream) {
 var configuration = {
 // Uncomment this code to add custom iceServers
 //"iceServers": [{ "url": "stun:stun.1.google.com:19302" }]" 
}]
 };
 yourConnection = new webkitRTCPeerConnection(configuration);
 theirConnection = new webkitRTCPeerConnection(configuration);
};

​ 在這裏,咱們定義函數來建立鏈接對象。在配置對象中,您能夠傳遞要在應用程序中使用的ICE服務器的參數。要使用自建ICE服務器,只需取消註釋代碼並更改值便可。瀏覽器將自動獲取配置並在進行對等鏈接時使用它。此時,這不是必需的,由於瀏覽器應該具備一組默認的ICE服務器。在此以後,咱們建立兩個對等鏈接對象來表明咱們應用程序中的每一個用戶。仍請記住,咱們的兩個用戶都將在此應用程序的同一瀏覽器窗口中。

建立SDP offer和響應

​ 在本節中,咱們將執行offer和響應應答程序以創建對等鏈接。咱們的下一個代碼塊將在兩個對等體之間設置提供和響應應答流:

function startPeerConnection(stream) {
 var configuration = {
// Uncomment this code to add custom iceServers
 //"iceServers": [{ "url": "stun:stun.1.google.com:19302" }]
 };
 yourConnection = new webkitRTCPeerConnection(configuration);
 theirConnection = new webkitRTCPeerConnection(configuration);
 // Begin the offer
 yourConnection.createOffer(function (offer) {
 yourConnection.setLocalDescription(offer);
 theirConnection.setRemoteDescription(offer);
 theirConnection.createAnswer(function (offer) {
 theirConnection.setLocalDescription(offer);
 yourConnection.setRemoteDescription(offer);
 });
 });
};

​ 你可能注意到的一件事是,通過整整一章的解釋,這段代碼看起來至關簡單。這是由於兩個對等體都在同一個瀏覽器窗口中。這樣,咱們能夠保證其餘用戶什麼時候得到offer,而沒必要執行許多異步操做。

​ 以這種方式實現offer/answer機制使其更容易理解。您能夠清楚地看到所需的步驟以及成功建立對等鏈接所需的順序。若是您使用附加到瀏覽器的調試工具,則能夠執行這些步驟並在每一個步驟檢查RTCPeerConnection對象,以確切瞭解發生的狀況。

​ 在下一章中,咱們將更深刻地探討這個主題。一般,您要鏈接的另外一個對等體不在同一個瀏覽器中 - 這意味着須要服務器來鏈接瀏覽器窗口之間的對等體。這使得這個過程變得更加複雜,由於這些步驟不只須要按照這裏顯示的確切順序進行,並且還要跨多個瀏覽器窗口進行。這須要在可能有時不穩定的環境中進行大量同步操做。

尋找ICE候選

​ 創建對等鏈接的最後一部分是在對等體之間傳輸ICE候選者,以便它們能夠相互鏈接。您如今能夠將startPeerConnection函數更改成以下所示:

function startPeerConnection(stream) {
 var configuration = {
 // Uncomment this code to add custom iceServers
 //"iceServers": [{ "url": "stun:127.0.0.1:9876" }]
 };
 yourConnection = new webkitRTCPeerConnection(configuration);
 theirConnection = new webkitRTCPeerConnection(configuration);
 // Setup ice handling
 yourConnection.onicecandidate = function (event) {
 if (event.candidate) {
 theirConnection.addIceCandidate(new 
RTCIceCandidate(event.candidate));
 }
 };
 theirConnection.onicecandidate = function (event) {
 if (event.candidate) {
 yourConnection.addIceCandidate(new 
RTCIceCandidate(event.candidate));
 }
 };
 // Begin the offer
 yourConnection.createOffer(function (offer) {
 yourConnection.setLocalDescription(offer);
theirConnection.setRemoteDescription(offer);
 theirConnection.createAnswer(function (offer) {
 theirConnection.setLocalDescription(offer);
 yourConnection.setRemoteDescription(offer);
 });
 });
};

​ 您可能會注意到這部分代碼徹底由事件驅動。這是因爲找到ICE候選者的異步性質。瀏覽器將不斷尋找候選者,直到它找到了認爲能夠建立對等鏈接或創建穩定對等鏈接的數量。
​ 在接下來的章節中,咱們將構建實際經過信令通道發送此數據的功能。須要注意的一點是,當咱們從他們的鏈接中得到ICE候選者時,咱們將其添加到yourConnection,反之亦然。咱們必須經過互聯網才能鏈接到不在同一地方的用戶。

添加和修飾數據流

​ 使用WebRTC能夠輕鬆地將流添加到對等鏈接。API負責設置流並經過網絡發送數據的全部工做。當其餘用戶將流添加到其對等鏈接時,將經過該鏈接發送此通知,通知第一個用戶該更改。而後,瀏覽器調用onaddstream通知用戶已添加流:

// Setup stream listening
 yourConnection.addStream(stream);
 theirConnection.onaddstream = function (e) {
 theirVideo.srcObject = e.stream;
 };

​ 而後,咱們能夠經過爲流的位置建立對象URL,將此流添加到本地視頻。這樣作是建立一個標識瀏覽器中的流的值,以便視頻元素能夠與之交互。這充當咱們視頻流的惟一ID,告訴視頻元素播放來自本地流的視頻數據做爲源。
​ 最後,咱們將爲應用程序添加一些樣式。視頻通訊應用程序最流行的風格是Skype等應用程序中常見的風格。今天,許多使用WebRTC構建的演示都複製了這一點。一般,您呼叫的人位於應用程序的前面和中間,而您本身的攝像頭顯示爲較大的一個小窗口。因爲咱們正在構建一個Web頁面,所以能夠經過一些簡單的CSS實現,以下所示:

<style>
 body {
 background-color: #3D6DF2;
 margin-top: 15px;
 }
video {
 background: black;
 border: 1px solid gray;
 }
 #container {
 position: relative;
 display: block;
 margin: 0 auto;
 width: 500px;
 height: 500px;
 }
 #yours {
 width: 150px;
 height: 150px;
 position: absolute;
 top: 15px;
 right: 15px;
 }
 #theirs {
 width: 500px;
 height: 500px;
 }
 </style>

​ 只需將其添加到您的HTML頁面,您就應該有一個良好的WebRTC應用程序。此時,若是您仍然認爲咱們的應用程序看起來很單調,請隨時繼續爲應用程序添加樣式。咱們將在接下來的章節中對此進行構建,而且使用一些CSS作一些更使人興奮的演示。

運行您的第一個WebRTC應用程序

​ 如今,運行您的網頁進行測試。當您運行該頁面時,它應該要求您與瀏覽器共享您的相機。一旦接受,它將啓動WebRTC鏈接過程。瀏覽器迅速完成咱們到目前爲止討論的步驟,並建立一個鏈接。而後,您應該看到本身的兩個視頻,一個來自您的相機,另外一個是經過WebRTC鏈接進行流式傳輸。

做爲參考,如下是此示例中代碼的完整列表。如下是咱們的index.html文件中的代碼:

<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8" />
 <title>Learning WebRTC - Chapter 4: Creating a 
RTCPeerConnection</title>
 <style>
 body {
 background-color: #3D6DF2;
 margin-top: 15px;
 }
 video {
 background: black;
 border: 1px solid gray;
 }
 #container {
 position: relative;
 display: block;
 margin: 0 auto;
 width: 500px;
 height: 500px;
 }
#yours {
 width: 150px;
 height: 150px;
 position: absolute;
 top: 15px;
 right: 15px;
 }
 #theirs {
 width: 500px;
 height: 500px;
 }
 </style>
 </head>
 <body>
 <div id="container">
 <video id="yours" autoplay></video>
 <video id="theirs" autoplay></video>
 </div>
 <script src="main.js"></script>
 </body>
</html>

下面是mian.js文件內容:

function hasUserMedia() {
    navigator.getUserMedia = navigator.getUserMedia ||
        navigator.webkitGetUserMedia || navigator.mozGetUserMedia ||
        navigator.msGetUserMedia;
    return !!navigator.getUserMedia;
}

function hasRTCPeerConnection() {
    window.RTCPeerConnection = window.RTCPeerConnection ||
        window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
    return !!window.RTCPeerConnection;
}

var yourVideo = document.querySelector("#yours"),
    theirVideo = document.querySelector("#theirs"),
    yourConnection, theirConnection;
if (hasUserMedia()) {
    navigator.getUserMedia({video: true, audio: false}, function (stream) {
        yourVideo.srcObject = stream;
        if (hasRTCPeerConnection()) {
            startPeerConnection(stream);
        } else {
            alert("Sorry, your browser does not support WebRTC.");
        }
    }, function (error) {
        console.log(error);
    });
} else {
    alert("Sorry, your browser does not support WebRTC.");
}

function startPeerConnection(stream) {
    var configuration = {
        "iceServers": [{"url": "stun:stun.1.google.com:19302"}]
    };
    yourConnection = new webkitRTCPeerConnection(configuration);
    theirConnection = new webkitRTCPeerConnection(configuration);
    // Setup stream listening
    yourConnection.addStream(stream);
    theirConnection.onaddstream = function (e) {
        theirVideo.srcObject = e.stream;
    };
    // Setup ice handling
    yourConnection.onicecandidate = function (event) {
        if (event.candidate) {
            theirConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
        }
    };
    theirConnection.onicecandidate = function (event) {
        if (event.candidate) {
            yourConnection.addIceCandidate(new RTCIceCandidate(event.candidate));
        }
    };
    // Begin the offer
    yourConnection.createOffer(function (offer) {
        yourConnection.setLocalDescription(offer);
        theirConnection.setRemoteDescription(offer);
        theirConnection.createAnswer(function (offer) {
            theirConnection.setLocalDescription(offer);
            yourConnection.setRemoteDescription(offer);
        });
    });
};

自測題

Q1: UDP適合WebRTC鏈接是由於它不能確保數據的準確性,對仍是錯?

Q2: WebRTC標準的信令和協商部分是由瀏覽器徹底完成,對仍是錯?

Q3: 如下描述SDP正確的是:

  1. 一個WebRTC的配置文件
  2. 弄清楚支持視頻編碼的一種方式
  3. 你電腦的一個商業名片
  4. 一個迷惑的沒人理解的技術文檔

Q4: 交互式鏈接創建(ICE)有助於在典型的網絡設置中找到兩個客戶端之間清晰的路徑。對或錯?

Q5: 下列關於TURN說法不正確的是?

  1. 它須要比普通鏈接更多的帶寬和處理能力
  2. NAT穿透,TURN應該是最好的鏈接方式
  3. TURN服務器必須處理客戶端之間發送的每一個數據包
  4. TURN方法由瀏覽器提供

總結

​ 恭喜你作到這一點。若是您已成功完成本章,那麼您就能夠開始製做更大的WebRTC應用程序了。本章的目標不只是建立WebRTC應用程序,還要了解在流程的每一個步驟中發生的狀況。

​ 在本章以後,應該已經清楚WebRTC是一項複雜的技術。咱們介紹了WebRTC內部工做的大量信息。雖然如今不須要了解WebRTC如何在瀏覽器中實現,但瞭解主要部分如何協同工做將有助於您理解將要到來的示例。

​ 在本章中,咱們介紹瞭如何在瀏覽器中建立對等鏈接的內部工做方式。咱們介紹了支持UDPSDPICE的幾種技術。您如今應該對兩個瀏覽器如何找到彼此以及如何經過Internet進行通訊進行表面層次的理解。

​ 最好回顧一下咱們迄今爲止所涵蓋的材料,以充分了解WebRTC在咱們的示例中的工做原理。重要的是要注意每一個步驟和序列都很重要。這將有助於調試WebRTC應用程序中的問題,由於咱們將在後面的章節中介紹更多的複雜性。

​ 本書的其他部分將以此示例爲基礎,使其比目前複雜得多。咱們將添加功能,以便在多個不一樣環境中的多個瀏覽器中鏈接多個用戶。每一章都將參考WebRTC流程的一部分,深刻研究它,涵蓋常見的陷阱,並處理網絡穩定性和安全性等邊緣狀況。

​ 在下一章中,咱們將開始構建信令服務器以支持鏈接遠程用戶。這是咱們將經過本書其他部分使用的信令服務器的基礎。它還容許咱們建立咱們的第一個真正的呼叫應用程序,就像Google 環聊同樣。

相關文章
相關標籤/搜索