参考官网:https://webrtc.org/getting-started/overview

WebRTC包含两个技术:

一、媒体捕获设备

媒体捕获设备包括摄像机和麦克风,还包括屏幕捕获设备。

摄像机和麦克风设备

访问媒体设备时,最好提供尽可能详细的限制条件。虽然可以通过简单的限制条件打开默认摄像头和麦克风,但这样可能提供的不是最佳的媒体流。

1.1 获取摄像头/麦克风媒体流

打开摄像头/麦克风且找到可用的MediaStream(媒体数据流): navigator.mediaDevices.getUserMedia(),返回一个 promise,该 promise 会针对匹配媒体解析为 MediaStream。

1.2 枚举设备

navigator.mediaDevices.enumerateDevices(), 枚举所有的连接摄像头和麦克风设备,返回一个 promise,该 promise 可解析为 MediaDevicesInfo 数组(MediaDevicesInfo包含一个名为 kind,值为 audioinput、audiooutput 或 videoinput,表示媒体设备的类型)

async function getConnectedDevices(type) {
    //获取所有摄像头
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

async function openCamera(cameraId, minWidth, minHeight) {
    //指定一个deviceId,最小宽度和最小高度
    const constraints = {
        'audio': {'echoCancellation': true},
        'video': {
            'deviceId': cameraId,
            'width': {'min': minWidth},
            'height': {'min': minHeight}
            }
        }

    return await navigator.mediaDevices.getUserMedia(constraints);
}

const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
    // 打开第一个可用的分辨率为 1280x720 像素的摄像头
    const stream = openCamera(cameras[0].deviceId, 1280, 720);
}

1.3 监听设备变化

因为许多相机和麦克风通过 USB 连接,并且可以在应用生命周期内连接和断开连接。
监听各种媒体设备更改,可以监听navigator.mediaDevices的devicechange 事件。

// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
    const listElement = document.querySelector('select#availableCameras');
    listElement.innerHTML = '';
    cameras.map(camera => {
        const cameraOption = document.createElement('option');
        cameraOption.label = camera.label;
        cameraOption.value = camera.deviceId;
    }).forEach(cameraOption => listElement.add(cameraOption));
}

// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);

// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
    const newCameraList = getConnectedDevices('video');
    updateCameraList(newCameraList);
});

屏幕录制媒体流

navigator.mediaDevices.getDisplayMedia()

二、Trickle方式点对点连接

对等连接是 WebRTC 规范的一部分,用于处理将不同计算机上的两个应用连接起来以使用对等协议进行通信。对等方之间的通信可以是视频、音频或任意二进制数据(对于支持 RTCDataChannel API 的客户端)。

连接的核心就是与 ICE(Internet 连接建立)服务器通信(进行转发+整合),客户端(ICE 候选对象)都由一个 RTCPeerConnection 对象处理。此类的构造函数接受单个 RTCConfiguration 对象作为其参数。此对象定义了对等连接的设置方式,应包含有关要使用的 ICE 服务器的信息。

2.1 发送 Offer/answer

设置了本地和远程会话说明,它们就会了解远程对等方的功能

发送方

async function makeCall() {
    // ICE 服务描述
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            //接收到answer,设置为远端描述
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    // 创建信令
    const offer = await peerConnection.createOffer();
    //设置为本地描述
    await peerConnection.setLocalDescription(offer);
    // 通过信令通道发送信令
    signalingChannel.send({'offer': offer});
}

接收方

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

2.2 收集发送本地候选者

边发现边发送

// 第一部分:发送本地候选者
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        // 发现一个候选者,立即发给对方
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// 第二部分:接收对方候选者
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        // 收到对方的候选者,添加到本地连接
        await peerConnection.addIceCandidate(message.iceCandidate);
    }
});

2.3 Offer 与 ICE 候选者

其他最终连接是Offer和ICE候选者

时序图

三、Vanilla 方式点对点连接

等待所有ICE候选者都收集完了之后叠加到Offer/answer发送出去。

四、监听连接状态

RTCPeerConnection 添加了一个监听器,用于监听 connectionstatechange 事件。

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

五、视频流传输

发送:

const localStream = await getUserMedia({video: true, audio: true});
const peerConnection = new RTCPeerConnection(iceConfig);
localStream.getTracks().forEach(track => {
    peerConnection.addTrack(track, localStream);
});

接收:

const remoteVideo = document.querySelector('#remoteVideo');

peerConnection.addEventListener('track', async (event) => {
    const [remoteStream] = event.streams;
    remoteVideo.srcObject = remoteStream;
});

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐