WebRTC (Web Real-Time Communications) 是一項實時通訊技術,在 2011 年由 Google 提出,經過 10 年的發展,W3C 於 2021 年正式釋出 WebRTC 1.0 標準。
WebRTC 標準概括介紹了兩種不同的技術:媒體捕獲裝置和對等連線(P2P,Peer-to-Peer),可讓使用者無需安裝任何外掛或第三方軟體的情況下,實現共用桌面、檔案傳輸、視訊直播等功能。
下圖是官方給出的一張 WebRTC 整體架構設計圖:
由於各個瀏覽器對 WebRTC 的實現有所不同,因此 Google 官方提供了一個介面卡指令碼庫:adapter.js,省去很多相容工作。
本文的原始碼已上傳至 Github,有需要的可以隨意下載。
自拍是指通過攝像頭拍照生成圖片,先看下 HTML 結構,其實就 4 個元素。
<video id="video"></video> <button id="btn">拍照</button> <canvas id="canvas" width="300" height="300"></canvas> <img id="img" alt="照片"/>
1)getUserMedia()
然後在指令碼中宣告各個元素,通過 navigator.mediaDevices.getUserMedia() 方法獲取媒體流。
const video = document.getElementById('video'); const canvas = document.getElementById('canvas'); const btn = document.getElementById('btn'); const img = document.getElementById('img'); const size = 300; /** * 獲取媒體流 */ navigator.mediaDevices.getUserMedia({ video: { width: size, height: size, }, audio: false }).then((stream) => { video.srcObject = stream; video.play(); });
getUserMedia() 的引數是一個包含了video 和 audio 兩個成員的 MediaStreamConstraints 物件,上面程式碼將攝像頭的解析度限制為 300 x 300。
then() 中的 stream 引數是一個 MediaStream 媒體流,一個流相當於容器,可以包含幾條軌道,例如視訊和音訊軌道,每條軌道都是獨立的。
video 元素中的 src 和 srcObject 是一對互斥的屬性,後者可關聯媒體源,根據規範也可以是 Blob 或者 File 等型別的資料。
接著為按鈕繫結點選事件,並且在點選時從流中捕獲幀,畫到 Canvas 內,再匯出賦給 img 元素。
/** * 點選拍照 */ btn.addEventListener('click', (e) => { const context = canvas.getContext('2d'); // 從流中捕獲幀 context.drawImage(video, 0, 0, size, size); // 將幀匯出為圖片 const data = canvas.toDataURL('image/png'); img.setAttribute('src', data); }, false);
在下圖中,左邊是 video 元素,開啟攝像頭後就會有畫面,在點選拍照按鈕後,右邊顯示捕獲的幀。
2)enumerateDevices()
MediaDevices 提供了存取媒體輸入和輸出的裝置,例如攝像頭、麥克風等,得到硬體資源的媒體資料。
mediaDevices.enumerateDevices() 會得到一個描述裝置的 MediaDeviceInfo 的陣列。
其中 groupId 用於標識多個裝置屬於同一個物理裝置,例如一個顯示器內建了攝像頭和麥克風。
navigator.mediaDevices.enumerateDevices() .then((devices) => { devices.forEach((device) => { console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`); }); })
3)devicechange
當媒體裝置(例如麥克風、攝像頭等)連線到系統或從系統中移除時,devicechange 事件就會被傳送給裝置範例。
navigator.mediaDevices.ondevicechange = (event) => { };
event 引數沒有附加任何特殊的屬性。
Windows 系統採用的共用桌面協定是 RDP(Remote Desktop Protocal),另一種可在不同作業系統共用桌面的協定是 VNC(Virtual Network Console)。
像 TeamViewer 採用的就是後一種協定,而 WebRTC 的遠端桌面沒有采用傳統的 RDP、VNC 等協定,因為不需要遠端控制。
WebRTC 提供了 getDisplayMedia() 方法採集桌面,在使用上與之前的 getUserMedia() 方法類似。
navigator.mediaDevices.getDisplayMedia({ video: { width: 2000, height: 1000 } }).then((stream) => { video.srcObject = stream; video.play(); });
在重新整理頁面後,會要求選擇共用的桌面,包括整個螢幕、視窗或 Chrome 分頁。
WebRTC 的錄影包括錄製音訊和視訊兩種流,通過 Blob 物件將資料儲存成多媒體檔案。
1)MediaRecorder
WebRTC 提供了 MediaRecorder 類,它能接收兩個引數,第一個是遠端的 MediaStream 媒體流,第二個是設定項。
其設定項包括編解碼器、音視訊位元速率、容器的 MIME 型別(例如 video/webm、video/mp4 )等相關資訊。
先看個範例,HTML結構如下所示,一個 video 元素和兩個 button 元素:回放和下載。
<video id="video"></video> <button id="playback">回放</button> <button id="download">下載</button>
然後看下錄影的整體邏輯,和之前自拍一節類似,也需要呼叫 getUserMedia() 獲取媒體流。
在 then() 的回撥中範例化 MediaRecorder 類,並設定多媒體格式。
其中WebM是一個由Google資助,免版權費用的視訊檔格式;VP8是一個開放的影像壓縮格式。
const video = document.getElementById('video'); const playback = document.getElementById('playback'); const download = document.getElementById('download'); const size = 300; const chunks = []; // 一個由 Blob 物件組成的陣列 navigator.mediaDevices.getUserMedia({ video: { width: size, height: size, }, audio: true }).then((stream) => { // 設定多媒體格式 const options = { mimeType: 'video/webm;codecs=vp8' }; // 範例化錄製物件 const recorder = new MediaRecorder(stream, options); // 當收到資料時觸發該事件 recorder.ondataavailable = function(e) { chunks.push(e.data); // data 是一個可用的 Blob 物件 } // 開始錄製 recorder.start(10); });
recorder 的 dataavailable 事件會在收到資料時觸發,e 引數的 data 屬性是一個可用的 Blob 物件。
最後在開始錄製呼叫 start() 方法時,可以設定一個毫秒級的時間片,那麼在錄製時會按照設定的值分割成一個個單獨的區塊,而不是錄製一個非常大的整塊內容。
分塊可以提高效率和可靠性,如果是一整塊,那麼會變得越來越大,讀寫效率也會變差。
2)回放
首先根據 chunks 生成 Blob 物件,再根據 Blob 物件生成 URL 物件。
playback.addEventListener('click', () => { // 根據 chunks 生成 Blob 物件 const blob = new Blob(chunks, {type: 'video/webm'}); // 根據 Blob 物件生成 URL 物件 video.src = window.URL.createObjectURL(blob); video.play(); }, false);
URL.createObjectURL 是一個靜態方法,返回值是一個指定的 File 物件或 Blob 物件。
3)下載
首先與回放一樣,也是生成一個 URL 物件,然後建立 a 元素,將物件賦給 href 屬性。
並且要指定 download 屬性,告訴瀏覽器下載 URL 而不是導航。
download.addEventListener('click', (e) => { const blob = new Blob(chunks, {type: 'video/webm'}); const url = window.URL.createObjectURL(blob); // 建立 a 元素 const a = document.createElement('a'); a.href = url; // 指示瀏覽器下載 URL 而不是導航 a.download = 'test.webm'; a.click(); }, false);
參考資料:
Build the backend services needed for a WebRTC app