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

本文轉載自 segmentfault 的天鑲 的文章

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

如果對WebRTC和DataChannel不太瞭解的同學,可以先閱讀如下文章:
WebRTC的RTCDataChannel
使用WebRTC搭建前端視頻聊天室——信令篇
使用WebRTC搭建前端視頻聊天室——入門篇

老劉和老姚

當然服務器完全不參與其中,顯然是不可能的,用戶需要通過服務器上存儲的信息,才能確定需要和誰建立連接。這裏通過一個故事來講述建立連接的過程:

不如釣魚去

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

現在,老劉聽說老姚釣魚技術高超,想和老姚討論釣魚技巧。只要老劉和老姚相互之間知道對方的門牌號以及憑證,就可以串門了:

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

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

換個角度

我們把角色做一個映射:
- 老劉:瀏覽器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

這樣,就建立了一個點對點的信道,流程如下所示:

禮物

故事

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

這裏可以做如下類比:
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工具,這就不說了
- 如象棋這種雙人對戰遊戲,每一步的數據服務器時不關心的,所以完全可以點對點發送
- 一對一在線面試、在線教育,這其實是即時通信的一個業務方向
- 視頻裸(),當我沒說


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章