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 服务(支持多人、转码、录制)
相关推荐
换个昵称都难21 小时前
webrtc 拥塞控制GCC 和PCC
webrtc
Cxiaomu1 天前
React接入WebRTC实时视频实践
react.js·音视频·webrtc
AndyHuang19761 天前
WebRTC 强制 Relay 模式下 TCP 重连失败深度排查与优化实战
webrtc
换个昵称都难1 天前
webrtc pacing 平滑发包模块
webrtc
换个昵称都难1 天前
webrtc 音频混音介绍
音视频·webrtc
换个昵称都难2 天前
webrtc QOS-RemoteBitrateEstimator接收端带宽估计(1)
webrtc
换个昵称都难2 天前
webrtc QOS-RemoteBitrateEstimator接收端带宽估计-四个实例(2)
webrtc
都在酒里2 天前
【极致低延时】香橙派部署 MediaMTX 实现 WebRTC 推流,延时仅 500-800ms,比局域网 ffmpeg 拉流快近 10 倍!(附踩坑全记录)
linux·arm开发·ffmpeg·webrtc·orangepi·嵌入式软件
换个昵称都难2 天前
WebRTC QoS 实战:从原理到弱网优化
开发语言·php·webrtc