WebRTC 完整调用流程(前端纯 JS 实现,最简可运行)

一、核心 API 简述

  1. navigator.mediaDevices.getUserMedia:获取本地摄像头 / 麦克风媒体流
  2. RTCPeerConnection:WebRTC 核心,建立 P2P 连接、传输音视频
  3. SDP:会话描述协议(信令交换:Offer / Answer)
  4. ICE:网络穿透,自动协商直连地址

WebRTC 本身不含信令服务,需自行实现信令(WebSocket/Socket.IO 交换 SDP、ICE 候选)。

二、最简可运行 Demo(本地模拟双人通话,无后端)

该示例单页面模拟两端,不用后端,直接浏览器打开即可测试。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebRTC 本地调用演示</title>
    <style>
        video { width: 400px; border: 1px solid #ccc; }
    </style>
</head>
<body>
    <h3>本地视频</h3>
    <video id="localVideo" autoplay muted playsinline></video>
    <h3>远端视频</h3>
    <video id="remoteVideo" autoplay playsinline></video>

    <script>
        // 1. 获取 DOM
        const localVideo = document.getElementById('localVideo');
        const remoteVideo = document.getElementById('remoteVideo');

        // 配置:音视频轨道 + ICE 服务器(STUN 用于内网穿透)
        const pcConfig = {
            iceServers: [
                { urls: 'stun:stun.l.google.com:19302' }
            ]
        };

        let localStream;
        let pc1, pc2; // 模拟两个 Peer

        // 启动
        start();

        async function start() {
            try {
                // 步骤1:采集本地音视频流
                localStream = await navigator.mediaDevices.getUserMedia({
                    video: true,
                    audio: true
                });
                localVideo.srcObject = localStream;

                // 步骤2:创建两个 PeerConnection(模拟A、B两端)
                pc1 = new RTCPeerConnection(pcConfig);
                pc2 = new RTCPeerConnection(pcConfig);

                // 将本地流添加到 Peer
                localStream.getTracks().forEach(track => {
                    pc1.addTrack(track, localStream);
                });

                // 远端收到轨道,渲染视频
                pc2.ontrack = e => {
                    remoteVideo.srcObject = e.streams[0];
                };

                // ICE 候选互相交换(模拟信令转发)
                pc1.onicecandidate = e => e.candidate && pc2.addIceCandidate(e.candidate);
                pc2.onicecandidate = e => e.candidate && pc1.addIceCandidate(e.candidate);

                // 步骤3:创建 Offer
                const offer = await pc1.createOffer();
                await pc1.setLocalDescription(offer);

                // 步骤4:B 端接收 Offer,创建 Answer
                await pc2.setRemoteDescription(offer);
                const answer = await pc2.createAnswer();
                await pc2.setLocalDescription(answer);

                // 步骤5:A 端接收 Answer
                await pc1.setRemoteDescription(answer);

            } catch (err) {
                console.error('WebRTC 调用失败:', err);
                alert('请允许摄像头/麦克风权限,且使用 HTTPS / localhost');
            }
        }
    </script>
</body>
</html>

运行要求

  1. 必须 localhost / HTTPS 环境(浏览器安全策略,HTTP 公网无法调用摄像头)
  2. 浏览器授权摄像头、麦克风权限
  3. 现代浏览器(Chrome / Edge / Firefox)

三、标准 P2P 通话完整流程(真实线上架构)

整体链路

客户端A信令服务 (WebSocket/Socket.IO)客户端B ↔ WebRTC P2P

3.1. 标准调用步骤(时序)

1,本地媒体采集

js

运行

javascript 复制代码
const stream = await navigator.mediaDevices.getUserMedia({video:true,audio:true})

2,创建 RTCPeerConnection

3,本地轨道 addTrack 进 Peer

  • A 发起 Offer
    • pc.createOffer()setLocalDescription(offer)
    • 通过信令把 Offer 发给 B
  • B 收到 Offer
    • setRemoteDescription(offer)
    • createAnswer()setLocalDescription(answer)
    • 通过信令把 Answer 发回 A
  • A 收到 Answer
    • setRemoteDescription(answer)
  • ICE 候选交换(网络穿透)
    • 两端 onicecandidate 拿到候选,通过信令互发
    • 对方 addIceCandidate
  • 连接成功ontrack 收到远端流,播放

3.2. 纯客户端核心代码(分离两端)

发起方(A)

javascript 复制代码
// 拿到本地流后
const pc = new RTCPeerConnection(pcConfig);
localStream.getTracks().forEach(t => pc.addTrack(t, localStream));

// 收到远端流
pc.ontrack = e => remoteVideo.srcObject = e.streams[0];

// ICE 候选发送给对方
pc.onicecandidate = e => {
  if (e.candidate) socket.emit('ice', e.candidate);
};

// 创建并发送 Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
socket.emit('offer', offer);

// 接收对方 Answer
socket.on('answer', async answer => {
  await pc.setRemoteDescription(answer);
});

// 接收对方 ICE 候选
socket.on('ice', candidate => {
  pc.addIceCandidate(candidate);
});

接收方(B)

javascript 复制代码
const pc = new RTCPeerConnection(pcConfig);
localStream.getTracks().forEach(t => pc.addTrack(t, localStream));

pc.ontrack = e => remoteVideo.srcObject = e.streams[0];
pc.onicecandidate = e => e.candidate && socket.emit('ice', e.candidate);

// 接收 Offer
socket.on('offer', async offer => {
  await pc.setRemoteDescription(offer);
  const answer = await pc.createAnswer();
  await pc.setLocalDescription(answer);
  socket.emit('answer', answer);
});

// 接收 ICE
socket.on('ice', candidate => pc.addIceCandidate(candidate));

四、常用配置 & 功能扩展

1. 只开音频 / 只开视频

js

运行

javascript 复制代码
// 仅语音
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
// 仅画面
navigator.mediaDevices.getUserMedia({ audio: false, video: true })

2. 关闭音视频轨道(静音 / 关摄像头)

js

运行

javascript 复制代码
// 关闭摄像头
localStream.getVideoTracks()[0].enabled = false;
// 静音
localStream.getAudioTracks()[0].enabled = false;

3. 挂断通话

js

运行

javascript 复制代码
// 停止媒体流
localStream.getTracks().forEach(track => track.stop());
// 关闭连接
pc.close();

4. 多路流 / 屏幕共享

屏幕共享 API:

js

运行

javascript 复制代码
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });

五、常见报错 & 排查

  1. NotAllowedError

    • 原因:未授权权限 / 非 localhost/HTTPS
    • 解决:本地用 localhost,线上部署 HTTPS
  2. RTCPeerConnection 不存在

    • 原因:浏览器过低 / 禁用 WebRTC
    • 解决:升级 Chrome/Edge
  3. 能发信令但看不到画面

    • 大概率 ICE/STUN 穿透失败,更换可用 STUN/TURN 服务器

六、后端信令选型(极简)

前端 WebRTC 只负责媒体连接,信令必须单独做

  • 小型应用:Socket.IO(Node.js 最简)
  • 大型 / 分布式:原生 WebSocket、MQTT
  • 商用:直接用 Janus / SFU 服务(支持多人、转码、录制)
相关推荐
某林21214 小时前
ROS2 + WebRTC + MQTT 异构系统架构
架构·系统架构·机器人·硬件架构·webrtc·ros2
EasyGBS9 天前
RTSP、RTMP、HLS、FLV、WebRTC……国标GB28181视频平台EasyGBS五大流媒体协议,你的场景该用哪个?
音视频·webrtc
REDcker10 天前
libdatachannel 快速入门
c++·webrtc·datachannel
冯程14 天前
WebRTC 之 AllocationSequence 详解
webrtc·音视频开发
阿拉斯攀登16 天前
售货柜实战:IPC 拉流 → 抽帧 → YOLO 识别完整流水线
yolo·ffmpeg·音视频·webrtc·视频编解码
阿拉斯攀登16 天前
RTSP 拉流与录制:IPC 摄像头本地录像完整方案
ffmpeg·音视频·webrtc·实时音视频·视频编解码
阿拉斯攀登16 天前
数据通道:RTCDataChannel 可靠与不可靠传输
音视频·webrtc·实时音视频·媒体·视频编解码
REDcker17 天前
腾讯TRRO SDK深度剖析
音视频·webrtc·实时音视频·直播·rtc·腾讯
阿拉斯攀登17 天前
FFmpeg 转码详解:封装格式、编码器、码率控制
ffmpeg·音视频·webrtc·实时音视频·视频编解码
阿拉斯攀登17 天前
FFmpeg 入门:命令行的 20 个常用场景
ffmpeg·音视频·webrtc·视频编解码