Der Kern der Front-End-Audio- und Video-WebRTC-Echtzeitkommunikation

RTCPeerConnection

Die RTCPeerConnection-Klasse ist die Kernklasse im interaktiven Echtzeit-Audio- und -Videosystem, das WebRTC unter dem Browser verwendet, und stellt eine WebRTC-Verbindung vom lokalen Computer zum Remote-Ende dar. Diese Schnittstelle stellt Implementierungen von Methoden zum Erstellen, Verwalten, Überwachen und Schließen von Verbindungen bereit.

Wenn Sie die Socket-Entwicklung durchgeführt haben, können Sie leichter verstehen RTCPeerConnection, dass es sich tatsächlich um eine erweiterte Version von Socket handelt.

Das Kommunikationsprinzip von WebRTC erfordert Medienaushandlung, Netzwerkaushandlung und Signalservereinrichtung in realen Szenarien. Ich habe ein Bild gezeichnet, um den Kommunikationsprozess von WebRTC wie folgt zusammenzufassen:

Aber heute, um es einfach zu verdeutlichen  RTCPeerConnection, gehen wir nicht auf das Problem der Entwicklung und Einrichtung eines Signalisierungsservers ein, sondern versuchen der Einfachheit halber, die Audio- und Videokommunikation zwischen den beiden Enden auf derselben Seite zu simulieren.

Lassen Sie uns vorher einige der verwendeten APIs und die Schritte zum Herstellen einer Verbindung mit WebRTC verstehen.

Verwandte API

  • RTCPeerConnectionEine Schnittstelle stellt eine WebRTC-Verbindung vom lokalen Computer zum Remote dar. Diese Schnittstelle stellt Implementierungen von Methoden zum Erstellen, Verwalten, Überwachen und Schließen von Verbindungen bereit.

  • PC.createOfferErstellen Sie eine Offer-Methode, die SDP-Angebotsinformationen zurückgibt.

  • PC.setLocalDescriptionLegen Sie die lokalen SDP-Beschreibungsinformationen fest.

  • PC.setRemoteDescriptionLegen Sie die Remote-SDP-Beschreibungsinformationen fest, d. h. die von der anderen Partei gesendeten SDP-Daten.

  • PC.createAnswerErstellen Sie eine Answer-Antwort-Methode, die SDP-Antwortinformationen zurückgibt.

  • RTCIceCandidateWebRTC-Netzwerkinformationen (IP, Port usw.)

  • PC.addIceCandidateFügen Sie die IceCandidate-Informationen der anderen Partei zur PC-Verbindung hinzu, d. h. fügen Sie die Netzwerkinformationen der anderen Partei hinzu.

WebRTC-Verbindungsschritte

  • 1. Erstellen Sie ein RTCPeerConnection-Objekt für beide Enden der Verbindung und fügen Sie dem RTCPeerConnection-Objekt einen lokalen Stream hinzu.

  • 2. Erhalten Sie die lokale Medienbeschreibungsinformation (SDP) und tauschen Sie sie mit dem Peer aus.

  • 3. Erhalten Sie Netzwerkinformationen (Kandidat, IP-Adresse und Port) und tauschen Sie sie mit dem entfernten Ende aus.

  • Senden Sie mir eine private Nachricht, um die neuesten und vollständigsten C++- Audio- und Video- Lern- und Verbesserungsmaterialien zu erhalten, einschließlich ( C/C++ , Linux , FFmpeg , webRTC , rtmp , hls , rtsp , ffplay , srs ) .

Demo tatsächlicher Kampf

Zuerst fügen wir Videoelemente und Steuerschaltflächen hinzu, die eingeführt werden  adpater.js , um sich an jeden Browser anzupassen.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
    <style>
        video {
            width: 320px;
        }
    </style>
</head>
<body>
    <video id="localVideo" autoplay playsinline></video>
    <video id="remoteVideo" autoplay playsinline></video>

    <div>
        <button id="startBtn">打开本地视频</button>
        <button id="callBtn">建立连接</button>
        <button id="hangupBtn">断开连接</button>
    </div>
    <!-- 适配各浏览器 API 不统一的脚本 -->
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="./webrtc.js"></script>
</body>
</html>

Definieren Sie dann die Objekte, die wir verwenden werden.

// 本地流和远端流
let localStream;
let remoteStream;

// 本地和远端连接对象
let localPeerConnection;
let remotePeerConnection;

// 本地视频和远端视频
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

// 设置约束
const mediaStreamConstraints = {
    video: true
}

// 设置仅交换视频
const offerOptions = {
    offerToReceiveVideo: 1
}

Registrieren Sie als Nächstes Ereignisse für die Schaltfläche und implementieren Sie die zugehörige Geschäftslogik.

function startHandle() {
    startBtn.disabled = true;
    // 1.获取本地音视频流
    // 调用 getUserMedia API 获取音视频流
    navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
        .then(gotLocalMediaStream)
        .catch((err) => {
            console.log('getUserMedia 错误', err);
        });
}

function callHandle() {
    callBtn.disabled = true;
    hangupBtn.disabled = false;

    // 视频轨道
    const videoTracks = localStream.getVideoTracks();
    // 音频轨道
    const audioTracks = localStream.getAudioTracks();
    // 判断视频轨道是否有值
    if (videoTracks.length > 0) {
        console.log(`使用的设备为: ${videoTracks[0].label}.`);
    }
    // 判断音频轨道是否有值
    if (audioTracks.length > 0) {
        console.log(`使用的设备为: ${audioTracks[0].label}.`);
    }
    const servers = null;

    // 创建 RTCPeerConnection 对象
    localPeerConnection = new RTCPeerConnection(servers);
    // 监听返回的 Candidate
    localPeerConnection.addEventListener('icecandidate', handleConnection);
    // 监听 ICE 状态变化
    localPeerConnection.addEventListener('iceconnectionstatechange', handleConnectionChange)

    remotePeerConnection = new RTCPeerConnection(servers);
    remotePeerConnection.addEventListener('icecandidate', handleConnection);
    remotePeerConnection.addEventListener('iceconnectionstatechange', handleConnectionChange);
    remotePeerConnection.addEventListener('track', gotRemoteMediaStream);

    // 将音视频流添加到 RTCPeerConnection 对象中
    // 注意:新的协议中已经不再推荐使用 addStream 方法来添加媒体流,应使用 addTrack 方法
    // localPeerConnection.addStream(localStream);
    // 遍历本地流的所有轨道
    localStream.getTracks().forEach((track) => {
        localPeerConnection.addTrack(track, localStream)
    })

    // 2.交换媒体描述信息
    localPeerConnection.createOffer(offerOptions)
    .then(createdOffer).catch((err) => {
        console.log('createdOffer 错误', err);
    });
}

function hangupHandle() {
    // 关闭连接并设置为空
    localPeerConnection.close();
    remotePeerConnection.close();
    localPeerConnection = null;
    remotePeerConnection = null;
    hangupBtn.disabled = true;
    callBtn.disabled = false;
}

// getUserMedia 获得流后,将音视频流展示并保存到 localStream
function gotLocalMediaStream(mediaStream) {
    localVideo.srcObject = mediaStream; 
    localStream = mediaStream; 
    callBtn.disabled = false;
}

function createdOffer(description) {
    console.log(`本地创建offer返回的sdp:\n${description.sdp}`)
    // 本地设置描述并将它发送给远端
    // 将 offer 保存到本地
    localPeerConnection.setLocalDescription(description) 
        .then(() => {
            console.log('local 设置本地描述信息成功');
        }).catch((err) => {
            console.log('local 设置本地描述信息错误', err)
        });
    // 远端将本地给它的描述设置为远端描述
    // 远端将 offer 保存
    remotePeerConnection.setRemoteDescription(description) 
        .then(() => { 
            console.log('remote 设置远端描述信息成功');
        }).catch((err) => {
            console.log('remote 设置远端描述信息错误', err);
        });
    // 远端创建应答 answer
    remotePeerConnection.createAnswer() 
        .then(createdAnswer)
        .catch((err) => {
            console.log('远端创建应答 answer 错误', err);
        });
}

function createdAnswer(description) {
    console.log(`远端应答Answer的sdp:\n${description.sdp}`)
    // 远端设置本地描述并将它发给本地
    // 远端保存 answer
    remotePeerConnection.setLocalDescription(description)
        .then(() => { 
            console.log('remote 设置本地描述信息成功');
        }).catch((err) => {
            console.log('remote 设置本地描述信息错误', err);
        });
    // 本地将远端的应答描述设置为远端描述
    // 本地保存 answer
    localPeerConnection.setRemoteDescription(description) 
        .then(() => { 
            console.log('local 设置远端描述信息成功');
        }).catch((err) => {
            console.log('local 设置远端描述信息错误', err);
        });
}

// 3.端与端建立连接
function handleConnection(event) {
    // 获取到触发 icecandidate 事件的 RTCPeerConnection 对象 
    // 获取到具体的Candidate
    const peerConnection = event.target;
    const iceCandidate = event.candidate;

    if (iceCandidate) {
        // 创建 RTCIceCandidate 对象
        const newIceCandidate = new RTCIceCandidate(iceCandidate);
        // 得到对端的 RTCPeerConnection
        const otherPeer = getOtherPeer(peerConnection);

        // 将本地获得的 Candidate 添加到远端的 RTCPeerConnection 对象中
        // 为了简单,这里并没有通过信令服务器来发送 Candidate,直接通过 addIceCandidate 来达到互换 Candidate 信息的目的
        otherPeer.addIceCandidate(newIceCandidate)
            .then(() => {
                handleConnectionSuccess(peerConnection);
            }).catch((error) => {
                handleConnectionFailure(peerConnection, error);
            });
    }
}

// 4.显示远端媒体流
function gotRemoteMediaStream(event) {
    if (remoteVideo.srcObject !== event.streams[0]) {
        remoteVideo.srcObject = event.streams[0];
        remoteStream = event.streams[0];
        console.log('remote 开始接受远端流')
    }
}

 Schließlich müssen einige Protokollfunktionen und Hilfsfunktionen registriert werden.

function handleConnectionChange(event) {
    const peerConnection = event.target;
    console.log('ICE state change event: ', event);
    console.log(`${getPeerName(peerConnection)} ICE state: ` + `${peerConnection.iceConnectionState}.`);
}

function handleConnectionSuccess(peerConnection) {
    console.log(`${getPeerName(peerConnection)} addIceCandidate 成功`);
}

function handleConnectionFailure(peerConnection, error) {
    console.log(`${getPeerName(peerConnection)} addIceCandidate 错误:\n`+ `${error.toString()}.`);
}

function getPeerName(peerConnection) {
    return (peerConnection === localPeerConnection) ? 'localPeerConnection' : 'remotePeerConnection';
}

function getOtherPeer(peerConnection) {
    return (peerConnection === localPeerConnection) ? remotePeerConnection : localPeerConnection;
}

Wenn Sie mit dem gesamten Prozess vertraut sind, können Sie alle Protokollfunktionen einheitlich extrahieren und kapseln. Damit Sie den gesamten Prozess des Herstellens einer Verbindung mit WebRTC während des Lesens des Codes besser verstehen können, Es wird keine Extraktion durchgeführt.

Nun, wenn hier alles gut geht, haben Sie erfolgreich eine WebRTC-Verbindung aufgebaut, der Effekt ist wie folgt:

(schnappt sich die Pinguinpuppe des Jahres der Ratte am Tisch)

Ich denke du magst

Origin blog.csdn.net/m0_60259116/article/details/124429913
Empfohlen
Rangfolge