WebRTC本地插入多個轉發節點,模擬多節點轉發,造成延遲

AnRFDev發表於2021-12-23

網路延遲是一種比較常見的情況。在本地網頁上,我們可以建立多個RTCPeerConnection,增加轉發次數,來模擬出網路延遲的效果。
建立通話後,再往後面增加本地轉發節點。

準備

頁面準備,方便我們後面除錯

<div id="container">
    <h1><a href="https://an.rustfisher.com/webrtc/peerconnection/upgrade-to-video" title="">WebRTC插入多個轉發節點</a></h1>

    <div id="videos">
        <video id="video1" playsinline autoplay muted></video>
        <video id="video2" playsinline autoplay></video>
    </div>

    <section><input type="checkbox" id="audio"><label for="audio">包含音訊(>= Chrome 49)</label></section>

    <div id="buttons">
        <button id="start">開始</button>
        <button id="call" disabled>呼叫</button>
        <button id="insertRelay" disabled>插入轉發</button>
        <button id="hangup" disabled>結束通話</button>
    </div>
    <div id="status"></div>
</div>

<script src="../../src/js/adapter-2021.js"></script>
<script src="js/connection2.js"></script>
<script src="js/main.js"></script>

放上2個video和幾個button。引入WebRTC adapter和控制指令碼。

如果要使用官方的介面卡adapter,按下邊的地址來引入

<!-- <script src="../../src/js/adapter-2021.js"></script> -->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

控制

分為connection2.jsmain.js

connection2.js包含新建節點的邏輯。main.js控制主流程。

connection2.js

這裡能新建2個RTCPeerConnection,建立新的連線。

function doNothing() { }

function errFunc(context) {
    return function (error) {
        trace('報錯 ' + context + ': ' + error.toString);
    };
}

// 新建2個節點並建立連線
function Connection2(stream, handler) {
    let servers = null;
    let pc1 = new RTCPeerConnection(servers);
    let pc2 = new RTCPeerConnection(servers);

    pc1.addStream(stream);
    pc1.onicecandidate = function (event) {
        if (event.candidate) {
            pc2.addIceCandidate(new RTCIceCandidate(event.candidate),
                doNothing, errFunc('AddIceCandidate'));
        }
    };
    pc2.onicecandidate = function (event) {
        if (event.candidate) {
            pc1.addIceCandidate(new RTCIceCandidate(event.candidate),
                doNothing, errFunc('AddIceCandidate'));
        }
    };
    pc2.onaddstream = function (e) {
        handler(e.stream);
    };
    pc1.createOffer(function (desc) {
        pc1.setLocalDescription(desc);
        pc2.setRemoteDescription(desc);
        pc2.createAnswer(function (desc2) {
            pc2.setLocalDescription(desc2);
            pc1.setRemoteDescription(desc2);
        }, errFunc('pc2.createAnswer'));
    }, errFunc('pc1.createOffer'));
    this.pc1 = pc1;
    this.pc2 = pc2;
}

Connection2.prototype.close = function () {
    this.pc1.close();
    this.pc2.close();
};

Connection2(stream, handler)新建pc1和pc2,將傳入的stream作為資料來源交給pc1。
隨後在pc1和pc2之間建立連線。pc2接到資料流後再交回給handler

main.js

先拿到ui

'use strict';

// 獲取ui
const video1 = document.querySelector('video#video1');
const video2 = document.querySelector('video#video2');
const statusDiv = document.querySelector('div#status');
const audioCheckbox = document.querySelector('input#audio');
const startBtn = document.querySelector('button#start');
const callBtn = document.querySelector('button#call');
const insertRelayBtn = document.querySelector('button#insertRelay');
const hangupBtn = document.querySelector('button#hangup');

記錄一些變數

const connectionList = []; // 連線點
let localStream;
let remoteStream;

啟動,獲取資料流。可以選擇是否帶音訊流。拿到資料流後,交給video1顯示,並且記錄為localStream

function gotStream(stream) {
    video1.srcObject = stream;
    localStream = stream;
    callBtn.disabled = false;
}

function start() {
    startBtn.disabled = true;
    const options = audioCheckbox.checked ? { audio: true, video: true } : { audio: false, video: true };
    navigator.mediaDevices.getUserMedia(options)
        .then(gotStream)
        .catch(function (e) {
            alert(`獲取媒體失敗 ${e}`);
        });
}

發起呼叫

function gotremoteStream(stream) {
    remoteStream = stream;
    video2.srcObject = stream;
    console.log('接收到了傳輸後的資料流');
    statusDiv.textContent = `當前節點數: ${connectionList.length * 2}`;
    insertRelayBtn.disabled = false;
}

function call() {
    callBtn.disabled = true;
    insertRelayBtn.disabled = false;
    hangupBtn.disabled = false;
    console.log('呼叫!');
    connectionList.push(new Connection2(localStream, gotremoteStream));
}

呼叫的方法是call(),使用Connection2來建立第一級連線。
連線的記錄存放在connectionList

插入轉發和call有點類似,都使用了Connection2。但是輸入的是remoteStream

function insertRelay() {
    connectionList.push(new Connection2(remoteStream, gotremoteStream));
    insertRelayBtn.disabled = true;
}

多次呼叫insertRelay(),可以模擬出多層轉發的情況。轉發次數多了,視訊延遲得也就越明顯。

結束通話/結束方法hangup()

function hangup() {
    console.log('結束通話');
    while (connectionList.length > 0) {
        const pipe = connectionList.pop();
        pipe.close();
    }
    insertRelayBtn.disabled = true;
    hangupBtn.disabled = true;
    callBtn.disabled = false;
}

connectionList裡面的連線全部拿出來結束掉。
如果數量比較多,結束耗時也會比較長。

效果預覽

效果預覽請參考WebRTC插入多個轉發節點

可以嘗試多點選幾次插入轉發按鈕。觀察視訊的延遲情況。

小結

本地網頁可以通過增加節點的辦法,模擬出視訊傳輸延遲的效果。
Connection2(stream, handler)裡的程式碼寫的非常簡潔,也利於瞭解WebRTC傳輸建立的過程
從這個例子我們也可以看出,實際工程中要儘量減少中間節點。並且要優先選擇質量高的節點。

本文連結

相關文章