当前位置: 首页 > 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.jsqmd.com/news/1010413/

相关文章:

  • 2026年6月金属复合板厂家实力深度横评:标准+工艺+应用,谁是真正的行业标杆? - 品牌推荐
  • 深入解析Kafka消费者群组的分配机制
  • 2026年6月临沂黄金回收店终极选购指南:5家实测对比,靠谱变现就选这几家 - 品牌推荐
  • AGI临界点已至:四维能力坐标系实操指南
  • Oracle EBS 里 “子模块(AR/AP)多做 / 少做了凭证”,本质是 “发票 / 交易录错了金额”,用的还是
  • Hands-on Research Tutorial:从零基础到学术新星的全栈科研实战指南与详细使用教程
  • 大型行为模型(LBM)的技术突破与应用实践
  • Java SpringBoot+Vue3+MyBatis Web鲜牛奶订购系统系统源码|前后端分离+MySQL数据库
  • 还在被框架绑架?一文看懂“六边形架构”,让你的核心业务稳如泰山!
  • XDP程序的性能分析与优化
  • 2026年6月金属复合板厂家深度评测:从标准制定到智能制造,谁是行业实力派? - 品牌推荐
  • 手把手教你用STM32的定时器捕获功能,读取编码电机转速(附TB6612驱动代码)
  • webrtc源码解析概要介绍
  • UKF、EKF、PF怎么选?一张图看懂非线性滤波器的选型指南与避坑要点
  • 别再死记硬背对比学习论文了!从InstDisc到DINO,我用一张图帮你理清发展脉络
  • 从手机拆解看制造:一文读懂HDI板用的RCC、LDP这些材料到底有啥区别
  • 如何选择北京老房改造装修公司?2026年6月推荐TOP5评测格局重塑空间特点市场份额 - 品牌推荐
  • 基于西门子S71500的市政污水处理PLC控制系统设计131(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)
  • Visual C++ Redistributable AIO:Windows程序兼容性问题的终极解决方案
  • 基于西门子S71500的市政污水处理PLC控制系统设计132(设计源文件+万字报告+讲解)(支持资料、图片参考_降重降ai)
  • Epclusa吉三代每日治丙肝全基因型,头痛疲乏常见,严重肾损禁用
  • 2026年Q2成都管理咨询公司评测:聚焦重庆企业需求的品牌对比 - 优质品牌商家
  • 2026年6月北京老房改造装修公司推荐:TOP5排名专业评测旧房翻新防踩坑价格 - 品牌推荐
  • Python的UnitTest接口自动化实战(三)
  • 移动端人脸分割实战:从BiSeNet到Adobe最新模型,如何为你的App选型与优化?
  • 使用ChartJS实现堆叠柱状图
  • HarmonyOS PC 应用 Flex alignContent 详解——多行内容的整体分布控制
  • DJI A3飞控安装避坑指南:GPS干扰、震动与散热,这些细节决定飞行安全
  • 告别寄存器操作:用瑞萨RA FSP库驱动外设,5分钟搞定一个SPI通信
  • 用Java解决‘动物园栅栏’排队问题:从算法小白到AC的保姆级思路拆解