[[441095]]
不知你是否用過 web 版的視頻面試,或者 web 版在線會議,它們都支持分享屏幕、也能開啟攝像頭。這些都是瀏覽器上實現的,作為前端開發,是否好奇過這些功能的實現原理呢?
瀏覽器上的音視頻通信相關的能力叫做 WebRTC(real time communication),是隨著網速越來越快、音視頻需求越來越多,而被瀏覽器所實現的音視頻的標準 API。
音視頻通信的流程有五步:采集、編碼、通信、解碼、渲染。
這五步比較好理解,但是每一步都有挺多內容的。
今天我們就來實現下采集的部分,來快速入下門,直觀感受下 WebRTC 能做什么吧。
我們會實現屏幕的錄制、攝像頭的錄制,并且能夠回放錄制的內容,還支持下載。
那我們開始吧。
思路分析
瀏覽器提供了 navigator.mediaDevices.getDisplayMedia 和 navigator.mediaDevices.getUserMedia 的 api,分別可以用來獲取屏幕的流、麥克風和攝像頭的流。
從名字就可以看出來 getDisplayMedia 獲取的是屏幕的流,getUserMedia 獲取的是和用戶相關的,也就是麥克風、攝像頭這些的流。
獲取流之后設置到 video 的 srcObject 屬性上就可以實現播放。
如果想要錄制視頻,需要用 MediaRecorder 的 api,它可以監聽流中的數據,我們可以把獲取到的數據保存到數組中。然后回放的時候設置到另一個視頻的 srcObject 屬性就可以了。
下載也是基于 MediaRecorder 錄制的數據,轉成 blob 后通過 a 標簽觸發下載。
大概理清了思路,我們來寫下代碼。
代碼實現
我們在頁面放兩個 video 標簽,一個用于實時的看錄制的視頻,一個用于回放。
然后放幾個按鈕。
- <selection>
- <video autoplay id = "player"></video>
- <video id = "recordPlayer"></video>
- </selection>
- <section>
- <button id = "startScreen">開啟錄屏</button>
- <button id = "startCamera">開啟攝像頭</button>
- <button id = "stop">結束</button>
- <button id = "reply">回放</button>
- <button id = "download">下載</button>
- </selection>
“開始錄屏” 和 “開啟攝像頭” 按鈕點擊的時候都開啟錄制,但是方式不同。
- startScreenBtn.addEventListener('click', () => {
- record('screen');
- });
- startCameraBtn.addEventListener('click', () => {
- record('camera');
- });
一個是用 getUserMedia 的 api 來獲取麥克風、攝像頭數據,一個是用 getDisplayMedia 的 api 獲取屏幕數據。
- async function record(recordType) {
- const getMediaMethod = recordType === 'screen' ? 'getDisplayMedia' : 'getUserMedia';
- const stream = await navigator.mediaDevices[getMediaMethod]({
- video: {
- width: 500,
- height: 300,
- frameRate: 20
- }
- });
-
- player.srcObject = stream;
- }
指定下寬高和幀率等參數,把返回的流設置到 video 的 srcObject 屬性上,就可以實時看到對應的音視頻。
然后,還要做錄制,需要用 MediaRecorder 的 api,傳入 stream,然后調用 start 方法,開啟錄制。
- let blobs = [], mediaRecorder;
-
- mediaRecorder = new MediaRecorder(stream, {
- mimeType: 'video/webm'
- });
- mediaRecorder.ondataavailable = (e) => {
- blobs.push(e.data);
- };
- mediaRecorder.start(100);
start 的參數是分割的大小,傳入 100 代表每 100ms 保存一次數據。
監聽 dataavailable 事件,在其中把獲取到的數據保存到 blobs 數組中。
之后根據 blobs 數組生成 blob,就可以分別做回放和下載了:
回放:
- replyBtn.addEventListener('click', () => {
- const blob = new Blob(blobs, {type : 'video/webm'});
- recordPlayer.src = URL.createObjectURL(blob);
- recordPlayer.play();
- });
blob 要經過 URL.createObjectURL 的處理,才能作為 object url 來被播放。
下載:
- download.addEventListener('click', () => {
- var blob = new Blob(blobs, {type: 'video/webm'});
- var url = URL.createObjectURL(blob);
-
- var a = document.createElement('a');
- a.href = url;
- a.style.display = 'none';
- a.download = 'record.webm';
- a.click();
- });
生成一個隱藏的 a 標簽,設置 download 屬性就可以支持下載。然后觸發 click 事件。
目前為止,我們已經實現了麥克風、攝像頭、屏幕的錄制,支持了回放和下載。
這里也貼一份:
- <html>
- <head>
- <title>錄屏并下載</title>
- </head>
- <body>
- <selection>
- <video autoplay id = "player"></video>
- <video id = "recordPlayer"></video>
- </selection>
- <section>
- <button id = "startScreen">開啟錄屏</button>
- <button id = "startCamera">開啟攝像頭</button>
- <button id = "stop">結束</button>
- <button id = "reply">回放</button>
- <button id = "download">下載</button>
- </selection>
-
- <script>
- const player = document.querySelector('#player');
- const recordPlayer = document.querySelector('#recordPlayer');
- let blobs = [], mediaRecorder;
-
- async function record(recordType) {
- const getMediaMethod = recordType === 'screen' ? 'getDisplayMedia' : 'getUserMedia';
- const stream = await navigator.mediaDevices[getMediaMethod]({
- video: {
- width: 500,
- height: 300,
- frameRate: 20
- }
- });
- player.srcObject = stream;
-
- mediaRecorder = new MediaRecorder(stream, {
- mimeType: 'video/webm'
- });
- mediaRecorder.ondataavailable = (e) => {
- blobs.push(e.data);
- };
- mediaRecorder.start(100);
- }
-
- const downloadBtn = document.querySelector('#download');
- const startScreenBtn = document.querySelector('#startScreen');
- const startCameraBtn = document.querySelector('#startCamera');
- const stopBtn = document.querySelector('#stop');
- const replyBtn = document.querySelector('#reply');
-
- startScreenBtn.addEventListener('click', () => {
- record('screen');
- });
- startCameraBtn.addEventListener('click', () => {
- record('camera');
- });
-
- stopBtn.addEventListener('click', () => {
- mediaRecorder && mediaRecorder.stop();
- });
-
- replyBtn.addEventListener('click', () => {
- const blob = new Blob(blobs, {type : 'video/webm'});
- recordPlayer.src = URL.createObjectURL(blob);
- recordPlayer.play();
- });
-
- download.addEventListener('click', () => {
- var blob = new Blob(blobs, {type: 'video/webm'});
- var url = URL.createObjectURL(blob);
-
- var a = document.createElement('a');
- a.href = url;
- a.style.display = 'none';
- a.download = 'record.webm';
- a.click();
- });
- </script>
- </body>
- </html>
總結
音視頻通信分為 采集、編碼、通信、解碼、渲染 這五步,瀏覽器的音視頻通信相關的 API 叫做 WebRTC。
我們實現了下采集的部分來入門了下 WebRTC,還支持了回放和下載。
涉及到的 api 有 3 個:
- navigator.mediaDevices.getUserMedia:獲取麥克風、攝像頭的流
- navigator.mediaDevices.getDisplayMedia:獲取屏幕的流
- MediaRecorder:監聽流的變化,實現錄制
我們分別用前兩個 api 獲取到了屏幕、麥克風、攝像頭的流,然后用 MediaRecorder 做了錄制,把數據保存到數組中,之后生成了 Blob。
video 可以設置 srcObject 屬性為一個流,這樣能直接播放,如果設置 Blob 的話需要用 URL.createObjectURL 處理下。
下載的實現是通過 a 標簽指向 Blob 對象的 object url,通過 download 屬性指定下載行為,然后手動觸發 click 來下載。
我們學會了如何用 WebRTC 來采集數據,這是音視頻通信的數據來源,之后還要實現編解碼和通信才能是完整 RTC 流程,這些后續再深入。
我們直觀的感受了下 WebRTC 能做什么,是不是感覺這個領域也挺有趣的呢?