使用WebRTC搭建前端視頻聊天室——點對點通訊篇

WebRTC給咱們帶來了瀏覽器中的視頻、音頻聊天體驗。但我的認爲,它最實用的特性莫過於DataChannel——在瀏覽器之間創建一個點對點的數據通道。在DataChannel以前,瀏覽器到瀏覽器的數據傳遞一般是這樣一個流程:瀏覽器1發送數據給服務器,服務器處理,服務器再轉發給瀏覽器2。這三個過程都會帶來相應的消耗,佔用服務器帶寬不說,還減緩了消息從發送到接收的時間。其實最理想的方式就是瀏覽器1直接與瀏覽2進行通訊,服務器不須要參與其中。WebRTC DataChannel就提供了這樣一種方式。前端

若是對WebRTC和DataChannel不太瞭解的同窗,能夠先閱讀以下文章:
- WebRTC的RTCDataChannel
- 使用WebRTC搭建前端視頻聊天室——信令篇
- 使用WebRTC搭建前端視頻聊天室——入門篇git

老劉和老姚

固然服務器徹底不參與其中,顯然是不可能的,用戶須要經過服務器上存儲的信息,才能肯定須要和誰創建鏈接。這裏經過一個故事來說述創建鏈接的過程:github

不如釣魚去

一些背景:
- 老劉和老姚都住在同一個小區但不一樣的片區,小區很破舊,沒有電話
- 片區相互隔離且片區門口有個保安,保安只認識本身片區的人,遇到不認識的人就須要查詢憑證才能經過,而憑證須要找物業才能肯定
- 門衛老大爺認識小區裏的全部人可是不知道都住哪,有什麼消息均可以在出入小區的時候代爲傳達web

如今,老劉據說老姚釣魚技術高超,想和老姚討論釣魚技巧。只要老劉和老姚相互之間知道對方的門牌號以及憑證,就能夠串門了:面試

  1. 門衛老大爺認識老劉和老姚
  2. 老劉找物業肯定了本身片區的出入憑證,將憑證、本身的門牌號以及意圖告訴門衛老大爺,讓其轉交給老姚
  3. 老姚買菜歸來遇到門衛老大爺,門衛老大爺將老劉的消息傳達給老姚。因而老姚知道怎麼去老劉家了
  4. 老姚很開心,他也找物業獲取了本身小區的憑證,並將憑證、本身的門牌號等信息交給門衛老大爺,但願他傳達給老劉
  5. 老劉吃早餐回來遇到門衛老大爺,老大爺把老姚的小區憑證、門牌號等信息告訴老劉,這樣老劉就知道了怎麼去老姚家了

老劉和老姚相互之間知道了對方的門牌號和小區出入憑證,他們相互之間有什麼須要交流的直接串門就好了,消息再也不須要門衛老大爺來代爲傳達了chrome

換個角度

咱們把角色作一個映射:
- 老劉:瀏覽器1
- 老姚:瀏覽器2
- 片區:不一樣網段
- 保安:防火牆
- 片區憑證:ICE candidate
- 物業:ICE server
- 門牌號:session description
- 門衛老大爺:server瀏覽器

因而乎故事就變成了這樣:服務器

  1. 瀏覽器1和瀏覽器2在server上註冊,並保有鏈接
  2. 瀏覽器1從ice server獲取ice candidate併發送給server,並生成包含session description的offer,發送給server
  3. server發送瀏覽器1的offer和ice candidate給瀏覽器2
  4. 瀏覽器2發送包含session description的answer和ice candidate給server
  5. server發送瀏覽器2的answer和ice candidate給瀏覽器1

這樣,就創建了一個點對點的信道,流程以下所示:websocket

信令交互流程

禮物

故事

老劉和老姚已經能夠相互串門了,通過一段時間的交流感情愈來愈深。老姚的親友送了20斤葡萄給老姚,老姚決定送10斤給老劉。老姚畢竟年事已高,不可能一次帶10斤。因而乎,老姚將葡萄分紅了10份,每次去老劉家串門就送一份過去。session

這裏能夠作以下類比:
1. 10斤葡萄:一個文件(儘管文件分片沒有意義,葡萄分開還能夠單獨吃,可是實在找不到啥好的比喻了)
2. 分紅10份:將文件分片,轉成多個chunk
3. 老姚一次只能帶一斤:datachannel每次傳輸的數據量不宜太大(找到最合適的大小

這其實就是經過datachannel傳輸文件的方式,首先將文件分片,而後逐個發送,最後再統一的進行組合成一個新的文件

分片

經過HTML5的File API能夠將type爲file的input選中的文件讀取出來,並轉換成data url字符串。這也就爲咱們提供了很方便的分片方式:

var reader = new window.FileReader(file);
reader.readAsDataURL(file);
reader.onload = function(event, text) {
    chunkify(event.target.result);//將數據分片
};

組合

經過datachannel發送的分片數據,咱們須要將其進行組合,因爲是data url字符串,在接收到全部包以後進行拼接就能夠了。拼接完成後就獲得了一個文件完整的data url字符串,那麼咱們如何將這個字符串轉換成文件呢?

方案一:直接跳轉下載

既然是個dataurl,咱們直接將其賦值給window.location.href天然能夠下載,可是這樣下載是無法設定下載後的文件名的,這想想都蛋疼

方案二:經過a標籤下載

這個原理和跳轉下載相似,都是使用dataurl自己的特性,經過建立一個a標籤,將dataurl字符串賦值給href屬性,而後使用download肯定下載後的文件名,就能夠完成下載了。可是很快又有新問題了,稍微大一點的文件下載的時候頁面崩潰了。這是由於dataurl有大小限制

方案三:blob

其實能夠經過給a標籤建立blob url的方式來進行下載,這個沒有大小限制。可是咱們手上是dataurl,因此須要先進行轉換:

function dataURItoBlob(dataURI, dataTYPE) {
    var binary = atob(dataURI.split(',')[1]),
        array = [];
    for (var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
    return new Blob([new Uint8Array(array)], {
        type: dataTYPE
    });
}

得到blob後,咱們就能夠經過URL API來下載了:

var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
var blob = dataURItoBlob(data, 'octet/stream');
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
!moz && window.URL.revokeObjectURL(url);
a.parentNode.removeChild(a);

這裏有幾個點:
1. datachannel實際上是能夠直接傳送blob的,可是隻有ff支持,因此傳data url
2. chrome下載是直接觸發的,不會進行詢問,firefox會先詢問後下載,在詢問過程當中若是執行了revokeObjectURL,下載就會取消,囧

升級

如咱們所知,WebRTC最有特色的地方實際上是能夠傳輸getUserMedia得到的視頻、音頻流,來實現視頻聊天。但事實上咱們的使用習慣來看,通常人不會一開始就打開視頻聊天,並且視頻聊天時很消耗內存的(32位機上一個鏈接至少20M左右好像,也有可能有出入)。因此常見的需求是,先創建一個包含datachannel的鏈接用於傳輸數據,而後在須要時升級成能夠傳輸視頻、音頻。

看看咱們以前傳輸的session description,它其實來自Session Description Protocol。能夠看到wiki上的介紹:

The Session Description Protocol (SDP) is a format for describing streaming media initialization parameters.

這意味着什麼呢?咱們以前創建datachannel是沒有加視頻、音頻流的,而這個流的描述是寫在SDP裏面的。如今咱們須要傳輸視頻、音頻,就須要添加這些描述。因此就得從新得到SDP,而後構建offer和answer再傳輸一次。傳輸的流程和以前同樣,沒什麼區別。但這一次,咱們不須要傳輸任何的ice candidate,這裏我曾經遇到了坑,通過國外大大的點撥才明白過來。

from mattm: You do not need to send ICE candidates on an already established peer connection. The ICE candidates are to make sure the two peers can establish a connection through their potential NAT and firewalls. If you can already send data on the peer connection, ICE candidates will not do anything.

Peertc

我將datachannel和websocket組合,實現了一個構建點對點鏈接的庫Peertc,它提供很是簡潔的方式來創建鏈接和發送數據、文件和視頻/音頻流,詳情見github。走過路過的記得star一下哦,有什麼bug也很是但願可以提出來。

最後

WebRTC的點對點方式可以運用在不少場景:
- 如web qq這種Web IM工具,這就不說了
- 如象棋這種雙人對戰遊戲,每一步的數據服務器時不關心的,因此徹底能夠點對點發送
- 一對一在線面試、在線教育,這實際上是即時通訊的一個業務方向
- 視頻裸(),當我沒說

就醬,另外打個廣告及拉點搜索引擎權重:個人博客

相關文章
相關標籤/搜索