Ядро интерфейсной аудио- и видеосвязи WebRTC в реальном времени

внешний интерфейс WebRTC
Ядро интерфейсной аудио- и видеосвязи WebRTC в реальном времени

"Видимость: 🌟🌟🌟🌟🌟"

"Вкус: Синьцзянская жареная рисовая лапша"

"Время приготовления: 10 мин."

Эта статья попала в одноименный склад Github фронтенд-столовойgithub.com/Geekhyt, Добро пожаловать в кафетерий. Если вы считаете, что еда и вино по-прежнему вкусные, звезда станет большим поощрением для владельца кафетерия.

Изучив последние две серии столбцов, мы получили предварительное представление о внешнем аудио и видео и WebRTC.Пришло время ввести код для реализации демонстрации, чтобы по-настоящему ощутить прелесть общения WebRTC в реальном времени. Для тех, кто не видел, перейдите по ссылке:

RTCPeerConnection

RTCPeerConnectionКласс является основным классом в интерактивной аудио- и видеосистеме реального времени, использующей WebRTC под браузером, и представляет собой соединение WebRTC между локальным компьютером и удаленным концом. Этот интерфейс предоставляет реализации методов для создания, обслуживания, мониторинга и закрытия соединений.

Если вы хотите узнать больше об этом классе, вы можете перейти по этой ссылке,developer.Mozilla.org/this-cn/docs/…

На самом деле, если вы занимались разработкой сокетов, вам будет легче понятьRTCPeerConnection, который на самом деле является расширенной версией socket.

в последней серии столбцовПредварительное исследование WebRTC для внешнего аудио и видео.В , мы узнали о принципе связи WebRTC.В реальных сценариях требуется согласование мультимедиа, согласование сети и настройка сервера сигнализации.Я нарисовал картинку, чтобы обобщить процесс связи WebRTC следующим образом:

Но сегодня мы просто пытаемся выяснитьRTCPeerConnection, не рассматривая проблему разработки и настройки сигнального сервера, упростим задачу, на этот раз попробуем смоделировать аудио- и видеосвязь между двумя концами на одной странице.

Перед этим давайте разберемся с некоторыми API, которые будут использоваться, и шагами по установлению соединения с WebRTC.

Связанные API

  • RTCPeerConnectionИнтерфейс представляет собой WebRTC-соединение локального компьютера с удаленным. Этот интерфейс предоставляет реализации методов для создания, обслуживания, мониторинга и закрытия соединений.

  • PC.createOfferСоздайте метод Offer, который возвращает информацию о предложении SDP.

  • PC.setLocalDescriptionЗадайте локальную информацию описания SDP.

  • PC.setRemoteDescriptionУстановите информацию описания удаленного SDP, то есть данные SDP, отправленные другой стороной.

  • PC.createAnswerСоздайте метод ответа Answer, который возвращает информацию об ответе SDP.

  • RTCIceCandidateИнформация о сети WebRTC (IP, порт и т. д.)

  • PC.addIceCandidateДобавьте информацию IceCandidate другой стороны к соединению с ПК, то есть добавьте сетевую информацию другой стороны.

Шаги подключения WebRTC

  • 1. Создайте объект RTCPeerConnection для обоих концов соединения и добавьте локальный поток в объект RTCPeerConnection.

  • 2. Получите информацию о локальном носителе (SDP) и обменяйтесь ею с партнером.

  • 3. Получите сетевую информацию (кандидат, IP-адрес и порт) и обменяйтесь ею с удаленным концом.

Демонстрация реального боя

Сначала добавляем элементы видео и кнопки управления, вводимadpater.jsадаптироваться к каждому браузеру.

<!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>

Затем определите объекты, которые мы будем использовать.

// 本地流和远端流
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
}

Затем зарегистрируйте события для кнопки и реализуйте соответствующую бизнес-логику.

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 开始接受远端流')
    }
}

Наконец, необходимо зарегистрировать некоторые функции журнала и служебные функции.

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;
}

На самом деле, когда вы знакомы со всем процессом, вы можете единообразно извлекать и инкапсулировать все функции журнала.Чтобы вам было легче понять весь процесс установления соединения с WebRTC в процессе чтения кода, экстракция не производится.

Ну а если тут все хорошо, вы успешно установили WebRTC соединение, то эффект следующий:

(Хватает за столом куклу пингвина года Крысы)

Ссылаться на

  • «Создание живой аудио- и видеосистемы с нуля» Ли Чао
  • «Разработка аудио и видео WebRTC React+Flutter+Go Actual Combat» Кан Шаоцзюнь
  • developer.Mozilla.org/this-cn/docs/…

❤️Любовное тройное комбо

1. Если вы считаете, что еда и напитки в столовой все еще аппетитны, просто поставьте лайк и поддержите это, ваше"отличный"моя самая большая мотивация.

2. Обратите внимание на фронтальную столовую официального аккаунта,"Ешьте каждый прием пищи!"

3. Нравится, комментирует, пересылает === призывает больше!