4┃音視訊直播系統之瀏覽器中通過 WebRTC 進行桌面共享

autofelix發表於2022-05-13

一、共享桌面原理

  • 共享桌面在直播系統中是一個必備功能

  • 共享者:每秒鐘抓取多次螢幕,每次抓取的螢幕都與上一次抓取的螢幕做比較,取它們的差值,然後對差值進行壓縮;如果是第一次抓屏或切幕的情況,即本次抓取的螢幕與上一次抓取螢幕的變化率超過 80% 時,就做全屏的幀內壓縮。最後再將壓縮後的資料通過傳輸模組傳送到觀看端;資料到達觀看端後,再進行解碼,這樣即可還原出整幅圖片並顯示出來

  • 遠端控制端:當使用者通過滑鼠點選共享桌面的某個位置時,會首先計算出滑鼠實際點選的位置,然後將其作為引數,通過信令傳送給共享端。共享端收到信令後,會模擬本地滑鼠,即呼叫相關的 API,完成最終的操作

  • 共享桌面的過程:抓屏、壓縮編碼、傳輸、解碼、顯示、控制

 

二、抓取桌面

  • 瀏覽器 WebRTC 中提供了方法 var promise = navigator.mediaDevices.getDisplayMedia(constraints) 進行桌面的抓取

  • 共享桌面,大多數人知道的可能是RDP(Remote Desktop Protocal)協議,它是 Windows 系統下的共享桌面協議;還有一種更通用的遠端桌面控制協議 VNC(Virtual Network Console),它可以實現在不同的作業系統上共享遠端桌面,而 TeamViewer、Todesk 都是使用的該協議

  • 遠端桌面協議一般分為桌面資料處理與信令控制兩部分

  • 桌面資料:包括了桌面的抓取、編碼、壓縮、傳輸、解碼和渲染

  • 信令控制:包括鍵盤事件、滑鼠事件以及接收到這些事件訊息後的相關處理等

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>share desktop by WebRTC</title>
</head>

<body>
    <button onclick="shareDesktop()">抓取桌面</button>
</body>
<script>
    // 抓取桌面
    function shareDesktop() {
        // 只有在 PC 下才能抓取桌面
        if (IsPC()) {
            // 開始捕獲桌面資料
            navigator.mediaDevices.getDisplayMedia({ video: true })
                .then(getDeskStream)
                .catch(handleError);
            return true;
        }
        return false;
    }

    // 得到桌面資料流
    function getDeskStream(stream) {
        localStream = stream;
    }

    // 判斷是否是PC
    function IsPC() {
        var userAgentInfo = navigator.userAgent;
        var Agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
        var flag = true;
        for (var v = 0; v < Agents.length; v++) {
            if (userAgentInfo.indexOf(Agents[v]) > 0) {
                flag = false;
                break;
            }
        }
        return flag;
    }
</script>

</html>

 

4┃音視訊直播系統之瀏覽器中通過 WebRTC 進行桌面共享

 

三、桌面展示

  • 桌面採集後,就可以通過 HTML 中的<video>標籤將採集到的桌面展示出來

  • 當桌面資料被抓到之後,會觸發 getDeskStream 函式

  • 在該函式中將獲取到的 stream 與 video 標籤聯絡起來,這樣就可以顯示桌面了

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>share desktop by WebRTC</title>
</head>

<body>
    <video autoplay playsinline id="deskVideo"></video>
    <button onclick="shareDesktop()">抓取桌面</button>
</body>
<script>
    var deskVideo = document.querySelect("video/deskVideo");

    // 抓取桌面
    function shareDesktop() {
        // 只有在 PC 下才能抓取桌面
        if (IsPC()) {
            // 開始捕獲桌面資料
            navigator.mediaDevices.getDisplayMedia({ video: true })
                .then(getDeskStream)
                .catch(handleError);
            return true;
        }
        return false;
    }

    // 得到桌面資料流並播放
    function getDeskStream(stream) {
        localStream = stream;
        deskVideo.srcObject = stream;
    }

    // 判斷是否是PC
    function IsPC() {
        var userAgentInfo = navigator.userAgent;
        var Agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
        var flag = true;
        for (var v = 0; v < Agents.length; v++) {
            if (userAgentInfo.indexOf(Agents[v]) > 0) {
                flag = false;
                break;
            }
        }
        return flag;
    }
</script>

</html>

 

四、錄製桌面視訊

  • 錄製視訊其實在上一章中詳細說過,這裡就不再重複了,這裡只貼一下大概的邏輯程式碼

  • 首先通過 getDisplayMedia 方法獲取到本地桌面資料

  • 然後將該流當作引數傳給 MediaRecorder 物件

  • 並實現 ondataavailable 事件,最終將音視訊流錄製下來

  • 具體實現請參考上一篇文章自己進行完善

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>share desktop by WebRTC</title>
</head>

<body>
    <button onclick="startRecord()">開始錄製</button>
</body>
<script>
    var buffer;
    
    function startRecord() {
        // 定義一個陣列,用於快取桌面資料,最終將資料儲存到檔案中
        buffer = [];

        var options = {
            mimeType: 'video/webm;codecs=vp8'
        }

        if (!MediaRecorder.isTypeSupported(options.mimeType)) {
            console.error(`${options.mimeType} is not supported!`);
            return;
        }

        try {
            // 建立錄製物件,用於將桌面資料錄製下來
            mediaRecorder = new MediaRecorder(localStream, options);
        } catch (e) {
            console.error('Failed to create MediaRecorder:', e);
            return;
        }
        // 當捕獲到桌面資料後,該事件觸發
        mediaRecorder.ondataavailable = handleDataAvailable;
        mediaRecorder.start(10);
    }

    function handleDataAvailable(e) {
        if (e && e.data && e.data.size > 0) {
            buffer.push(e.data);
        }
    }
</script>

</html>

 

相關文章