WebRTC是一個免費的開源項目,它經過簡單的API爲瀏覽器和移動應用程序提供實時通訊功能。本文將向你展現WebRTC的基本概念和功能,並指導你使用Node.js構建本身的WebRTC視頻直播。css
先決條件:
- 具備Java經驗
- 掌握Socket.io基本知識
WebRTC基礎
WebRTC支持在網絡世界中進行實時通訊,主要用於在網絡上傳輸視頻和音頻數據。 在開始編寫代碼以前,咱們首先來看一下WebRTC的最重要概念。html
- 信令:
WebRTC用於瀏覽器中的通訊流,但還須要一種機制來協調通訊併發送控制消息,該過程稱爲信令。node
信令用於如下任務:express
- 初始化和關閉通信
- 與外界共享網絡配置(IP地址,端口)
- 報告鏈接錯誤
信令方法不是WebRTC指定的,開發人員能夠自行選擇(本教程將使用Socket.io)。npm
STUN和TURN服務器:
若是主要的WebRTC對等鏈接遇到問題,則將STUN和TURN服務器用做備用方法。 STUN服務器用於獲取計算機的IP地址,而TURN服務器用做對等鏈接失敗的中繼。小程序
既然咱們已經瞭解了WebRTC的基本概念,就能夠繼續開發上面討論的項目。微信小程序
使用Socket.io發出信號
在使用WebRTC經過對等鏈接發送視頻廣播以前,咱們首先須要使用信令方法(在本例中爲Socket.IO)實例化該鏈接。瀏覽器
爲此,咱們建立項目並使用npm安裝所需的依賴項:服務器
mkdir WebSocketsVideoBroadcast && cd WebSocketsVideoBroadcast npm install express socket.io --save
以後,咱們建立如下文件夾結構:微信
咱們從一個簡單的Socket.io服務器框架開始:
const express = require("express"); const app = express(); const port = 4000; const http = require("http"); const server = http.createServer(app); const io = require("socket.io")(server); app.use(express.static(__dirname + "/public")); io.sockets.on("error", e => console.log(e)); server.listen(port, () => console.log(`Server is running on port ${port}`));
而後,咱們須要實現客戶端和直播者與服務器的鏈接。 直播者的Socket ID保存到一個變量中,以便咱們之後知道客戶端須要鏈接到的位置。
let broadcaster io.sockets.on("connection", socket => { socket.on("broadcaster", () => { broadcaster = socket.id; socket.broadcast.emit("broadcaster"); }); socket.on("watcher", () => { socket.to(broadcaster).emit("watcher", socket.id); }); socket.on("disconnect", () => { socket.to(broadcaster).emit("disconnectPeer", socket.id); }); });
以後,咱們將實現socket.io事件以初始化WebRTC鏈接。 雙方將使用這些事件來實例化對等鏈接。
socket.on("offer", (id, message) => { socket.to(id).emit("offer", socket.id, message); }); socket.on("answer", (id, message) => { socket.to(id).emit("answer", socket.id, message); }); socket.on("candidate", (id, message) => { socket.to(id).emit("candidate", socket.id, message); });
這就是咱們Socket.io的服務器實現的所有內容,如今咱們能夠繼續進行佈局以及雙方通訊的實現。
Layouts
咱們的佈局由兩個基本HTML文件組成,其中包含一個視頻視圖(稍後將顯示咱們正在發送的視頻流)和一個CSS文件(用於某些基本樣式)。
index.html文件包含一個視頻視圖,該視圖將顯示來自廣播公司的視頻流。 它還會導入socket.io依賴項和咱們的watch.js文件。
<!DOCTYPE html> <html> <head> <title>Viewer</title> <meta charset="UTF-8" /> <link href="/styles.css" rel="stylesheet"> </head> <body> <video playsinline autoplay></video> <script src="/socket.io/socket.io.js"></script> <script src="/watch.js"></script> </body> </html>
broadcast.html文件與主佈局很是類似,但會導入broadcast.js文件而不是watch.js。
<!DOCTYPE html> <html> <head> <title>Broadcaster</title> <meta charset="UTF-8" /> <link href="/styles.css" rel="stylesheet"> </head> <body> <video playsinline autoplay muted></video> <script src="/socket.io/socket.io.js"></script> <script src="/broadcast.js"></script> </body> </html>
我還爲視頻視圖提供了一些簡單的CSS樣式。
html { overflow: hidden; height: 100%; } video { width: 100%; height: 100%; position: absolute; display: block; top: 0; left: 0; object-fit: cover; } body { background-color: black; margin: 0; height: 100%; width: 100%; position: fixed; top: 0; left: 0; bottom: 0; right: 0; }
RTCPeerConnection
RTCPeerConnections幫助咱們將位於本地網絡中的兩臺計算機相互鏈接。 在談論這些類型的鏈接時,會涉及到不少術語:
- ICE-互聯網鏈接創建
- STUN-經過網絡地址轉換器[NAT]進行的用戶數據報協議[UDP]的會話遍歷
因爲當今大多數設備都在NAT路由器後面,所以沒法直接鏈接。 這就是爲何必須由STUN服務器初始化對等鏈接的緣由,STUN服務器將返回咱們能夠鏈接的ICE候選對象。
在本指南中,咱們有兩個不一樣的鏈接部分。 一個是視頻直播方,能夠與客戶端創建多個對等鏈接,並使用流發送視頻。 第二個是客戶端,它與當前視頻直播方只有一個鏈接。
直播方
首先,咱們爲對等鏈接和攝像機建立配置對象。
const peerConnections = {}; const config = { iceServers: [ { urls: ["stun:stun.l.google.com:19302"] } ] }; const socket = io.connect(window.location.origin); const video = document.querySelector("video"); // Media contrains const constraints = { video: { facingMode: "user" } // Uncomment to enable audio // audio: true, };
咱們使用正式的google STUN服務器進行點對點鏈接,並使用媒體限制條件配置攝像機。 你也能夠經過取消註釋音頻線路來啓用音頻。
在建立對等鏈接以前,咱們首先須要從攝像機獲取視頻,以便將其添加到咱們的鏈接中。
navigator.mediaDevices .getUserMedia(constraints) .then(stream => { video.srcObject = stream; socket.emit("broadcaster"); }) .catch(error => console.error(error));
接下來,咱們將使用如下代碼建立一個RTCPeerConnection:
socket.on("watcher", id => { const peerConnection = new RTCPeerConnection(config); peerConnections[id] = peerConnection; let stream = video.srcObject; stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); peerConnection.onicecandidate = event => { if (event.candidate) { socket.emit("candidate", id, event.candidate); } }; peerConnection .createOffer() .then(sdp => peerConnection.setLocalDescription(sdp)) .then(() => { socket.emit("offer", id, peerConnection.localDescription); }); }); socket.on("answer", (id, description) => { peerConnections[id].setRemoteDescription(description); }); socket.on("candidate", (id, candidate) => { peerConnections[id].addIceCandidate(new RTCIceCandidate(candidate)); });
每次有新客戶端加入時,咱們都會建立一個新的RTCPeerConnection並將其保存在咱們的peerConnections對象中。
而後,咱們使用addTrack()方法將本地流添加到鏈接中,並傳遞流和跟蹤數據。
當咱們收到一個ICE候選者時,將調用peerConnection.onicecandidate事件,並將其發送到咱們的服務器。
以後,咱們經過調用peerConnection.createOffer()將鏈接提議發送給客戶端,而後調用peerConnection.setLocalDescription()來配置鏈接。
當客戶端斷開鏈接時,關閉鏈接是應用程序的另外一個重要部分,咱們可使用如下代碼來實現:
socket.on("disconnectPeer", id => { peerConnections[id].close(); delete peerConnections[id]; });
最後,若是用戶關閉窗口,咱們將關閉socket鏈接。
window.onunload = window.onbeforeunload = () => { socket.close(); };
客戶端
客戶端(觀看視頻的一方))具備幾乎相同的功能。 惟一的區別是,他僅打開了與當前視頻直播方的一個對等鏈接,而且他獲取了視頻,而不是流式傳輸視頻。
咱們還須要爲RTCPeerConnection建立一個配置。
let peerConnection; const config = { iceServers: [ { urls: ["stun:stun.l.google.com:19302"] } ] }; const socket = io.connect(window.location.origin); const video = document.querySelector("video");
而後,咱們能夠建立咱們的RTCPeerConnection並從視頻直播方獲取視頻流。
socket.on("offer", (id, description) => { peerConnection = new RTCPeerConnection(config); peerConnection .setRemoteDescription(description) .then(() => peerConnection.createAnswer()) .then(sdp => peerConnection.setLocalDescription(sdp)) .then(() => { socket.emit("answer", id, peerConnection.localDescription); }); peerConnection.ontrack = event => { video.srcObject = event.streams[0]; }; peerConnection.onicecandidate = event => { if (event.candidate) { socket.emit("candidate", id, event.candidate); } }; });
在這裏,咱們像上面同樣使用配置對象建立了一個新的RTCPeerConnection。 惟一的區別是,咱們調用createAnswer()函數將鏈接應答發送回視頻直播方的請求。
創建鏈接後,咱們能夠繼續使用peerConnection對象的ontrack事件偵聽器獲取視頻流。
咱們還須要爲點對點鏈接實現其餘生命週期功能,這將有助於咱們打開和關閉新鏈接。
socket.on("candidate", (id, candidate) => { peerConnection .addIceCandidate(new RTCIceCandidate(candidate)) .catch(e => console.error(e)); }); socket.on("connect", () => { socket.emit("watcher"); }); socket.on("broadcaster", () => { socket.emit("watcher"); }); window.onunload = window.onbeforeunload = () => { socket.close(); peerConnection.close(); };
至此,該應用程序已完成,能夠繼續在瀏覽器中對其進行測試。
測試應用程序
如今咱們已經完成了該應用程序,是時候對其進行測試,看看它是否能夠工做了。
咱們可使用如下命令啓動該應用程序:
node server.js
該應用程序如今應該在你的localhost:4000上運行,而且能夠經過鏈接到localhost:4000 / broadcast來添加新的視頻直播品程序進行測試。
以後,只須要訪問localhost:4000便可做爲客戶端鏈接到服務器,而且你應該得到從視頻直播方的流式傳輸的視頻。
結論
我但願本文能幫助您瞭解WebRTC的基礎知識以及如何使用它來流式傳輸視頻直播。
EasyRTC視頻會議雲服務
基於WebRTC技術而開發的EasyRTC,是TSINGSEE青犀視頻團隊在音視頻領域多年的技術積累而研發的, 它是覆蓋全球的實時音頻開發平臺,支持一對1、一對多等視頻通話。
EasyRTC擁有MCU和SFU兩種架構,無需安裝客戶端與插件,純H5在線視頻會議系統,支持微信小程序、H5頁面、APP、PC客戶端等接入方式,極大知足語音視頻社交、在線教育和培訓、視頻會議和遠程醫療等場景需求。
隨着移動互聯網的高速發展,AI、5G等等新興技術的到來,結合WebRTC技術,也將衍生出更多的應用場景,改變人類的衣、食、住、行等生活方式。