《Learning WebRTC中文版》試讀 + 簽名優惠版

《Learning WebRTC中文版》出版啦,外刊君是本書的翻譯和審校階段組織者。本文是該書的 第2章:獲取用戶媒體,歡迎試讀。獲取本書的簽名優惠版,見文末

獲取用戶媒體

創建一個基於WebRTC的通信平台,首先需要通過用戶的網路攝像頭和麥克風獲取實時的視頻和音頻流。在過去的瀏覽器中,我們通常用插件來實現這個功能;而現在,我們可以用JavaScript調用瀏覽器的getUserMedia API來實現。

本章將圍繞以下幾個話題展開:

  • 如何訪問媒體設備
  • 如何約束媒體流
  • 如何處理多種設備
  • 如何修改流數據

訪問媒體設備

在很久之前,開發者們就開始嘗試將媒體設備接入瀏覽器中,他們曾糾結於各種解決方案,有的是基於Flash的,而有的基於插件,但這些方案都需要用戶在瀏覽器中安裝某些程序才能捕捉到攝像頭。為此,W3C最終決定籌備一個專門的小組來制定相關標準。在最新的瀏覽器中,你可以通過JavaScript訪問getUserMedia API,它又被稱為MediaStream API。

譯註:目前getUserMedia API已被廢除,請使用MediaStream API。

這組API有以下幾個關鍵功能:

- 提供一個stream對象:這個對象用以表示音頻或視頻形式的實時媒體流。

- 提供設備間切換的功能:當多個攝像頭或麥克風連接到計算機上時,可以選擇所需設備。

- 提供充分的安全保障:獲得用戶的訪問許可,根據偏好設置從用戶的計算機設備捕獲數據流。

在我們繼續進行之前,需要提前準備好相應的編程環境。首先,要有一個可以編輯HTML和JavaScript的文本編輯器,凡是購買這本書的讀者很可能已經有一個趁手的編輯器了。

其次,你還需要一台伺服器來託管HTML和JavaScript文件並提供伺服服務。瀏覽器的許可權及安全限制要求:必須是通過真實伺服器伺服的文件才可以連接用戶的攝像頭和麥克風。如果你在本地雙擊打開本書提供的代碼,它將不會正常運行。

配置靜態伺服器

開發者們都應該先學會如何配置一台本地的Web伺服器,編程語言多種多樣,用不同語言編寫的伺服器也數不勝數,我個人最喜愛的是Node.js的node-static,這是一個出色易用的Web伺服器:

  1. 訪問Node.js網站nodejs.org/,點擊首頁那個巨大的INSTALL按鈕,在操作系統中安裝Node.js;


    譯註:Node.js現分為兩個分支獨立發展,建議使用v4.4.* LTS分支),下載後根據指引操作可將Node.js安裝到你的操作系統中。

  2. Node.js的包管理器(npm)會隨Node.js一同安裝到系統中;

  3. 打開終端或命令行界面並輸入npm install -g node-static(你很可能需要管理員許可權);

  4. 選擇一個你希望用來提供伺服服務的目錄,置入相關的HTML文件;

  5. 運行static指令啟動一個靜態web伺服器,在瀏覽器中輸入http://localhost:8080即可訪問你的文件。

對於其它的HTML文件,你也可以通過類似的方式為其提供伺服服務,也可以將本書提供的示例文件放到目錄下訪問來查看效果。

儘管除了node-static外還有其它很多選擇,但由於我們稍後需要使用npm,所以非常建議你現在就了解它的相關語法。

現在就繼續創建我們的第一個項目!

在開始之前,你應該確保至少有一個攝像頭以及麥克風連接到你的計算機上。大多數計算機都有相關的設置選項,你需要測試攝像頭並確保一切可以正常工作!

創建我們的首個媒體流頁面

我們的首個支持WebRTC的頁面很簡單:在屏幕上展示一個<video>元素,請求使用攝像頭後在<video>元素里實時顯示它此刻拍攝到的內容。video是HTML5里的一個強大的特性,既可以通過它展示實時的視頻流,也能用它回放很多其它的視頻源。我們開始先創建一個簡單的HTML頁面,要在body標籤里包含一個video元素。你可以創建一個名為index.html的文件,並且輸入以下代碼:

<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>Learning WebRTC - Chapter 2: Get User Media</title> </head> <body> <video autoplay></video> <script src="main.js"></script> </body></html>

切記WebRTC是一個徹頭徹尾的HTML5特性,你必須使用一個支持HTML5標準的新瀏覽器。在上面的代碼中,我們通過DOCTYPE標籤將瀏覽器標記為兼容HTML5的標準模式。

如果此時打開頁面,你會略感失望,你將看到一個空白頁面,這是因為我們沒有載入main.js文件導致的。我們來添加一個main.js文件,在裡面貼入一段getUserMedia的代碼:

function hasUserMedia() { return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);}if (hasUserMedia()) { navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; navigator.getUserMedia({ video: true, audio: true }, function (stream) { var video = document.querySelector("video"); video.src = window.URL.createObjectURL(stream); }, function (err) {});} else { alert("抱歉,你的瀏覽器不支持 getUserMedia.");}

現在刷新頁面,你應該能夠看到一切都運行起來了!首先,你會看到一個授權彈框,它與之前運行WebRTC Demo時看到的那個類似。如果選擇同意,它就會獲得攝像頭的許可權並在頁面上的<video>元素里顯示它拍到的內容。

各家瀏覽器總喜歡領先於官方標準實現一些新特性,然後再等待這些特性成為標準,只有正確處理瀏覽器前綴問題才能讓新的瀏覽器API正常運行。他們會創建一些與瀏覽器名字相似的前綴,例如Chrome的Webkit,Firefox的Moz。這樣做可以告訴瀏覽器這些不是標準API,需要針對它進行特殊處理。然而不幸的是,這也導致了在多個瀏覽器中需要調用不同的方法來訪問同一個API。我們最終克服了這個困難,做法是:創建一個函數,檢測這些非標準API在當前瀏覽器中是否可用,如果可用,則將這些API全都賦值給一個普通的函數,然後在之後的代碼中調用這個函數即可。

接下來,我們要訪問getUserMedia函數。這個函數接受一組參數(來確定瀏覽器將要做的事情)和一個回調函數,這個回調只接受一個參數:當前計算機上能夠產生數據流的媒體設備。

這個對象指向一個瀏覽器為我們保持的媒體流。它會不斷從攝像頭和麥克風捕獲數據,等待來自web應用的指令來操作這些數據。我們稍後會獲取屏幕上的<video>元素並通過window.URL.createObjectURL函數將流載入到該元素中,由於<video>元素不能接受JavaScript作為參數,它只能通過一些字元串來獲取視頻流,這個函數在獲取流對象後會將它轉換成一個本地的URL,這樣<video>元素就能從這個地址獲取流數據了。

請注意,<video>元素包含一個autoplay屬性,表示視頻流位元組處理完成後會自動播放,如果你移除這個屬性,數據流接入時不會自動播放。

現在,你已經在構建WebRTC應用的路上邁出了第一步!通過頁面上的這些代碼,你可以看到getUserMedia API的實際威力,其實,從攝像頭獲取stream對象並導入頁面上的視頻元素這個過程並不簡單,僅就這一話題就可以寫一整本關於C或C++的書!

限制媒體流

現在我們了解了從瀏覽器獲取流的方法,馬上來學習如何通過getUserMediaAPI的第一個參數配置這個流。這個參數接受一個對象,通過其鍵值可以確定從已連接設備尋覓並處理流的方法。我們要講的第一個選項是開關視頻或音頻流:

navigator.getUserMedia({ video: false, audio: true }, function(stream) { // 現在我們的數據流里不包含任何視頻!});

在這個示例中,當你將流添加到<video>元素時,不會顯示來自攝像頭的視頻;如果對調兩個配置,則只有視頻沒有音頻。如果你不想在開發WebRTC應用時聽一整天自己說的話,那麼這是極好的!

通常來說,在開發WebRTC應用的時候可能會遇到音頻反饋現象,當麥克風捕捉到你的聲音再通過揚聲器播放出來,會產生一個永無止境的迴響。在開發過程中暫時關閉音頻有助於解決這個問題。

示例的效果請看下面這張截圖,訪問localhost/8080的時候展示了一個下拉彈出框,這表明頁面需要獲取訪問麥克風的許可權:

如果你想開發一款用來代替普通電話的應用,它理應只支持音頻呼叫,這種配置方法會屏蔽視頻數據。如果有人不想分享他們的視頻,也可以只請求訪問瀏覽器中的麥克風,從而也不會打開攝像頭指示燈。

限制視頻捕捉

你不僅可以通過設置true或false值來限制getUserMedia API,也可以傳遞一個對象進行更複雜的限制。請在tools.ietf.org/html/dra查找限制方法的相關規範細節,例如:最低解析度、幀速率、視頻寬高比還有一些可選項,這些都可以通過配置對象傳遞給getUserMedia API。

這可以幫助開發者應對創建WebRTC應用時遇到的不同場景。通過這些選項開發者可以根據用戶當前場景從瀏覽器請求更具體的流類型。其中的一些流列出如下:

* 創建良好的用戶體驗,在每一位參與視頻呼叫的用戶間選取最小解析度來請求

* 保持特定風格或品牌形象,在應用中設置特定的寬和高

* 在受限的網路連接中限制視頻流的解析度來節省電力或帶寬

舉個例子,我們就假設希望將回放視頻設置成16:9的長寬比,那麼在一個像4:3這樣更小的長寬比環境下,將無法正常導入視頻。如果你在調用getUserMedia時傳入以下配置,將會強制改為指定的長寬比:

navigator.getUserMedia({ video: { mandatory: { minAspectRatio: 1.777, maxAspectRatio: 1.778 }, optional: { { maxWidth: 640 }, { maxHeigth: 480 } } }, audio: false}, function (stream) { var video = document.querySelector("video"); video.src = window.URL.createObjectURL(stream);}, function (error) { console.log("Raised an error when capturing:", error);});

刷新瀏覽器後並授權頁面捕捉攝像頭,現在顯示的視頻比以前更寬。在配置對象的第一部分,我們將長寬比限制為16:9或1.777。在optional這一部分,我們將解析度限制為640×480。如果可以的話,瀏覽器將根據optional代碼塊的設置盡量嘗試平衡這些需求。你所看到的視頻解析度很可能是640×360,這是在當前限制下大多數攝像頭普遍支持的解決方案。

你應該也注意到我們在調用getUserMedia時傳入了第二個函數,當捕獲媒體流時遇到任何問題都會調用這個出錯回調函數。在之前的示例中,如果你的攝像頭不支持16:9的解析度,就會觸發這個函數。請時刻注意瀏覽器中的開發者控制台,看看當錯誤發生時是否會調用這個出錯回調。如果當前項目成功運行,你也可以試著更改minAspectRatio或maxAspectRatio來檢測瀏覽器支持的其它參數:

現在,我們可以根據用戶的使用環境適配不同情形,提供最好的視頻流體驗,每個用戶的瀏覽器環境不盡相同,因此這些配置非常有用。如果有很多用戶使用你的WebRTC應用,就必須為每個獨立的使用環境提供獨立的解決方案。支持移動端設備是數個最大的痛苦之一,這些設備的運行資源有限,其屏幕空間也捉襟見肘。如果要節約電量、處理器和帶寬,在手機上可以按照480×320或更小的解析度來捕獲視頻。通過對比瀏覽器的user agent字元串與常見的移動web瀏覽器,可以檢測用戶是否在使用移動設備。將getUserMedia調用改成以下這段代碼可以實現上述功能:

var constraints = { video: { mandatory: { minWidth: 640, minHeight: 480 } }, audio: true};if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { // 用戶正在使用移動設備,請降低我們的最小解析度 constraints = { video: { mandatory: { minWidth: 480, minHeight: 320, maxWidth: 1024, maxHeight: 768 } }, audio: true };}navigator.getUserMedia(constraints, function (stream) { var video = document.querySelector("video"); video.src = window.URL.createObjectURL(stream);}, function (error) { console.log("Raised an error when capturing:", error);});

你必須要重視限制配置的重要性,通過簡單的調整就可以有效提升WebRTC應用的性能。當通讀本章後,你應當考慮應用未來的使用環境,及每種環境下最好的解決方案,在第8章 高級安全和大規模優化中我們將深入探究如何優化WebRTC的性能。

多設備處理

在某些情況下,用戶可能在他們的設備上接駁多台攝像頭或麥克風。現在的移動手機基本都有前置和後置兩個攝像頭,此時你需要查遍所有可用的攝像頭和麥克風,然後選擇適當的設備來滿足用戶的需求。好在瀏覽器暴露出了一個名為MediaSourceTrack的API,可以很方便地管理多個設備。

在編寫本書時,只有最新版本的Chrome支持MediaSourceTrack API,由於很多類似的API仍在被創造的過程中,因此不是所有瀏覽器都支持這些特性。

我們可以通過MediaSourceTrack請求設備列表並從中選擇我們所需的設備:

MediaStreamTrack.getSources(function(sources) { var audioSource = null; var videoSource = null; for (var i = 0; i < sources.length; ++i) { var source = sources[i]; if(source.kind === "audio") { console.log("發現麥克風:", source.label, source.id); audioSource = source.id; } else if (source.kind === "video") { console.log("發現攝像頭:", source.label, source.id); videoSource = source.id; } else { console.log("發現未知資源:", source); } } var constraints = { audio: { optional: [{sourceId: audioSource}] }, video: { optional: [{sourceId: videoSource}] } }; navigator.getUserMedia(constraints, function (stream) { var video = document.querySelector("video"); video.src = window.URL.createObjectURL(stream); }, function (error) { console.log("Raised an error when capturing:", error); });});

在上面這段代碼中,我們調用MediaSourceTrack對象的getSources方法返回一個連接到用戶機器的設備列表,遍歷後可以選一個與你的應用更契合的設備。如果運行代碼時開發控制台是打開的,你會發現當前連接到計算機上的設備都被列印出來了。例如,我的電腦有兩個麥克風和一個攝像頭,代碼運行後會列印出如下圖所示的內容:

source對象中也包含其它的一些信息,比如有助於設備選擇的朝向信息。經過日積月累,瀏覽器或可提供更多的信息,例如:解析度、幀率(FPS)以及更多設備的信息。記得時常查看getUserMedia和MediaStreamTrack這兩個API的更新信息,你可以了解到各個瀏覽器中新增的特性。

創建一個拍照室應用

Web平台最棒的地方在於,它的所有子集可以很好地協同運轉,通過Canvas就可以輕鬆地創建一個複雜的拍照室應用。這個應用能夠讓你在屏幕上看到自己,也可以隨時捕捉自己的照片,就像一個真的拍照室一樣。Canvas是一系列在屏幕上繪製線條、圖形和圖片的API,因其可以製作遊戲並且實現其它交互應用,從而在Web平台上流行起來。

在這個項目中,我們將使用Canvas API繪製其中一幀視頻到屏幕上。從<video>元素里獲取當前的流,將其轉換為一張圖片,再把圖片繪製到<canvas>元素中。我們通過一個簡單的HTML文件來啟動項目:

<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>Learning WebRTC - Chapter 2: Get User Media</title> <style> video, canvas { border: 1px solid gray; width: 480px; height: 320px; } </style> </head> <body> <video autoplay></video> <canvas></canvas> <button id="capture">Capture</button> <script src="photobooth.js"></script> </body></html>

下載示例代碼

如果你購買過Packt出版的書,便可以在http://www.packtpub.com通過你自己的賬號下載實例代碼文件。如果你在其它地方購買的本書,可以訪問packtpub.com/support註冊已購的圖書,我們將通過郵件把代碼發給您。

我們需要在頁面上添加canvas標籤,然後載入photobooth.js文件,我們的JavaScript文件里全是邏輯功能代碼:

function hasUserMedia() { return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);}if (hasUserMedia()) { navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; var video = document.querySelector("video"), canvas = document.querySelector("canvas"), streaming = false; navigator.getUserMedia({ video: true, audio: false }, function (stream) { video.src = window.URL.createObjectURL(stream); streaming = true; }, function (error) { console.log("Raised an error when capturing:", error); }); document.querySelector("#capture").addEventListener("click", function (event) { if (streaming) { canvas.width = video.clientWidth; canvas.height = video.clientHeight; var context = canvas.getContext("2d"); context.drawImage(video, 0, 0); } });} else { alert("對不起,您的瀏覽器不支持getUserMedia.");}

現在,如果點擊Capture按鈕便可捕捉視頻並將其中的一幀繪製到canvas上。此時的<canvas>元素里顯示的是一個靜止的幀。你也可以一遍又一遍地保存截圖來替換canvas上的圖片。

修改媒體流

我們可以繼續改進這個項目。如今的大多數圖片分享應用總會準備很多濾鏡,你可以給圖片使用濾鏡讓它們看起來更酷。在Web平台上通過CSS濾鏡就可以提供不同的效果。我們先編寫幾個CSS類,再將這些濾鏡應用到<canvas>元素:

<style> .grayscale { -webkit-filter: grayscale(1); -moz-filter: grayscale(1); -ms-filter: grayscale(1); -o-filter: grayscale(1); filter: grayscale(1); } .sepia { -webkit-filter: sepia(1); -moz-filter: sepia(1); -ms-filter: sepia(1); -o-filter: sepia(1); filter: sepia(1); } .invert { -webkit-filter: invert(1); -moz-filter: invert(1); -ms-filter: invert(1); -o-filter: invert(1); filter: invert(1); }</style>

然後,我們再添加一些JavaScript,當用戶點擊的時候改變濾鏡:

var filters = ["", "grayscale", "sepia", "invert"], currentFilter = 0;document.querySelector("video").addEventListener("click", function (event) { if (streaming) { canvas.width = video.clientWidth; canvas.height = video.clientHeight; var context = canvas.getContext("2d"); context.drawImage(video, 0, 0); currentFilter++; if(currentFilter > filters.length - 1) currentFilter = 0; canvas.className = filters[currentFilter]; }});

當你載入了這個頁面,無論何時你從攝像頭拍了一個新的快照,它總會被應用新的濾鏡。CSS濾鏡的威力強大,可以動態修改Canvas輸出的內容,瀏覽器會為你處理一切,比如應用濾鏡和展示新圖片。

一旦可以通過這種途徑把流導入canvas,將有無限的可能。canvas是一個低階且強有力的繪圖工具,支持很多功能,例如:繪製線條、圖形和文字等。舉個例子,下面這段代碼可以給你的圖片添加一些文字:

context.fillStyle = "white";context.fillText("Hello World!", 10, 10);

當捕捉到圖片時,你應該可以看到圖片左上角角落裡有一行字:Hello World!,請大膽地使用Canvas API修改文字、尺寸或其它內容。在此之上更進一步的是WebGL,這項技術支持在瀏覽器中渲染3D物體,集JavaScript之大成且效果驚人。你可以把一個流視頻源當做WebGL中的紋理貼在3D空間里的對象上!網路上有成千上萬個相關的示例,我建議你多看看,了解瀏覽器強大的功能。

自測題

Q1. 在瀏覽器中,打開自文件的頁面和從web伺服器請求的頁面均可以訪問攝像頭和麥克風。對還是錯?

Q2. 下列哪一個是不正確的瀏覽器前綴?

1. webkitGetUserMedia

2. mozGetUserMedia

3. blinkGetUserMedia

4. msGetUserMedia

Q3. getUserMedia API的第三個參數接受一個函數,當從攝像頭或麥克風獲取數據流時如果有錯誤發生則調用這個函數。對還是錯?

Q4. 下列哪一個不是限制視頻流的好處?

1. 給視頻流加密

2. 節省運算處理所消耗的電力

3. 提供一個好的用戶體驗

4. 節省帶寬

Q5. getUserMedia API可以與Canvas API和CSS濾鏡結合,給應用添加更多的功能。對還是錯?

總結

到目前為止,你應該掌握了如何通過不同的方式捕捉麥克風和攝像頭的數據流。我們同樣也介紹了限制數據流以及從多個設備中做出選擇的方法。在最後一個項目中,我們將所有的功能組織起來,結合濾鏡和圖像捕捉的功能打造了一款獨立的拍照室應用。

在本章中,我們介紹了如何訪問媒體設備,限制媒體流,處理多設備,修改流數據等內容。

MediaStream規範正在不斷更新中,目前計劃為這個API添加更多新特性,以此來構建更有趣的Web應用。當你開發WebRTC應用時,請時刻關注最新的規範,關注各大瀏覽器廠商的最新動態。

本章的內容儘管看起來不多,在接下來的章節中將發揮重大作用。後續我們將了解如何修改輸入的流來確保WebRTC應用正常運行。

在接下來的章節中,我們將使用在本章中習得的知識通過WebRTC技術給另一位用戶發送我們的數據流。

該書已經在各大網上書店開賣了,京東,噹噹網,亞馬遜;

[已結束]獲取《Learning WebRTC中文版》簽名優惠版

  • 原價65.00元,優惠價49.00元;
  • 外刊君簽名 + 你希望寫上的一句話;
  • 支付方式:通過知乎專欄或者微信公眾號文章的讚賞49.00元訂購,通過微信公眾號或者外刊君私信溝通郵寄地址;
  • 多謝支持,歡迎訂購!

推薦閱讀:

使用 WebRTC 構建簡單的前端視頻通信
Kurento是否可以讓客戶端選擇不同的實時監控視頻?
WebRTC有前途嗎?

TAG:JavaScript | WebRTC | 视频直播 |