当前位置: 首页 > news >正文

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

一、核心 API 简述

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

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

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

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

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

运行

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)

// 拿到本地流后 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)

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

运行

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

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

js

运行

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

3. 挂断通话

js

运行

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

4. 多路流 / 屏幕共享

屏幕共享 API:

js

运行

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 服务(支持多人、转码、录制)
http://www.zskr.cn/news/1522227.html

相关文章:

  • 2026年6月金属复合板厂家实力深度横评:标准+工艺+应用,谁是真正的行业标杆? - 品牌推荐
  • AGI临界点已至:四维能力坐标系实操指南
  • 2026年6月临沂黄金回收店终极选购指南:5家实测对比,靠谱变现就选这几家 - 品牌推荐
  • 深入解析Kafka消费者群组的分配机制
  • Hands-on Research Tutorial:从零基础到学术新星的全栈科研实战指南与详细使用教程
  • 从手机拆解看制造:一文读懂HDI板用的RCC、LDP这些材料到底有啥区别
  • 基于西门子S71500的市政污水处理PLC控制系统设计131(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)
  • 2026年Q2成都管理咨询公司评测:聚焦重庆企业需求的品牌对比 - 优质品牌商家
  • 移动端人脸分割实战:从BiSeNet到Adobe最新模型,如何为你的App选型与优化?
  • Python的UnitTest接口自动化实战(三)
  • DJI A3飞控安装避坑指南:GPS干扰、震动与散热,这些细节决定飞行安全
  • 三步掌握微信小程序逆向工程:从小白到高手的完整指南
  • 用Java解决‘动物园栅栏’排队问题:从算法小白到AC的保姆级思路拆解
  • 终极指南:如何用XUnity.AutoTranslator轻松玩转外文Unity游戏
  • 磁编码器选型笔记:为什么我为我的项目选择了昆泰芯KTH7823的PWM输出方案?
  • 2026年6月金属复合板厂家推荐:从建筑幕墙到高端装饰,选对厂家让工程品质与效率双赢 - 品牌推荐
  • SAP月结提速秘籍:巧用CK11N和CK24,避免成本发布中的常见‘坑’
  • MuleSoft驱动的企业级AI编排:让大模型真正融入业务流程
  • M9A重返未来1999智能助手:3分钟快速上手指南
  • 机器学习模型生产化落地:构建高可运维性推理服务
  • Python的UnitTest接口自动化实战(四)
  • 从图形渲染到机器学习:深入聊聊向量点积与叉积那些意想不到的实用场景
  • 2026亚洲EMBA中立排行榜:理性择校全维度测评
  • 伪谱法、有限元、有限差分怎么选?一张图讲清三大数值方法优缺点与适用场景
  • 西门子PLC与DCS通讯的二选一:Modbus TCP无线方案 vs RTU有线方案深度对比
  • 告别FreeRTOS?聊聊汽车电子开发中AUTOSAR OS的独特优势与RTA-OS上手体验
  • 避坑指南:在Ubuntu 20.04上用KubeKey替代Sealos快速部署K8s,再一键安装DeepFlow社区版
  • RAID5 vs RAID6:从‘够用’到‘安全’,你的家庭NAS和公司服务器该怎么配?
  • CS5090EA vs 传统方案:在电动工具里实现双节锂电高效充电,我们实测了这些关键数据
  • 3步解锁第七史诗自动化挂机的完整解决方案