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

一、共享桌面原理

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

  • 共享者:每秒鐘抓取多次螢幕,每次抓取的螢幕都與上一次抓取的螢幕做比較,取它們的差值,然後對差值進行壓縮;如果是第一次抓屏或切幕的情況,即本次抓取的螢幕與上一次抓取螢幕的變化率超過 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>

 

 

三、桌面展示

  • 桌面採集後,就可以通過 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>