WebRTC实现视频通话

发布时间:2026/9/10 3:56:25

WebRTC实现视频通话 WebRTC实现内网下电脑和手机视频通话1.信令与信令服务器信令Signaling是 WebRTC 中用于建立、维护和终止连接的所有控制信息的统称。它本身不传输音视频数据只负责“协调双方如何开始通话”。主要涉及到的信令内容SDP协商offer/answer内容音视频格式、分辨率、编解码器、传输协议等“通话规格”。目的让双方确认“我们要用同一种语言讲话”。ICE候选candidate内容IP 地址、端口、传输协议UDP/TCP等“网络地址”。目的让双方知道“数据该往哪个物理地址发”。信令服务器在WebRTC中两个设备没有对方的IP地址也没法直接建立连接。信令服务器作为双方都能访问的公共节点让它们能互相找到彼此。2.视频通话实现思路核心逻辑2.1手机端和电脑端前端代码实现逻辑一致实现视频通话之前首先连接信令服务器这是后续两台设备传递信令的基础​ 前端连接信令服务器逻辑// ---------- 连接信令服务器 ----------functionconnectSignaling(){wsnewWebSocket(SIGNALING_SERVER_URL);ws.onopen(){setStatus(✅ 已连接到信令服务器等待对方...,#4CAF50);// 启用发起和接听按钮接听需要等对方发起才真正可用这里仅让界面可操作startBtn.disabledfalse;answerBtn.disabledfalse;// 但此时接听按钮按下其实还没用因为没有 offer后续逻辑会处理};......}​ 信令服务器代码逻辑importWebSocket,{WebSocketServer}fromws// import WebSocket from ws//创建websocket服务器监听端口constwssnewWebSocketServer({port:8090})//用于存储所有连接的客户端constclientsnewSet()console.log(信令服务器已启动监听端口: 8090);wss.on(connection,(ws){console.log(一个新客户端连接)clients.add(ws)//收到消息时转发给所有其他客户端ws.on(message,(message){console.log(收到消息并转发)//广播所有消息给其他客户端clients.forEach((client){if(client!wsclient.readyStateWebSocket.OPEN){client.send(message)}})})//客户端断开连接时清理ws.on(close,(){console.log(客户端断开了)clients.delete(ws)})})连接完成信令服务器后开始进行通话2.2首先在手机端点击通话按钮核心逻辑​获取本地流显示手机端的视频流在本地//1.获取本地视频流if(!localStream){awaitgetLocalStream();}asyncfunctiongetLocalStream(){try{conststreamawaitnavigator.mediaDevices.getUserMedia({video:true,audio:true});localVideo.srcObjectstream;localStreamstream;returnstream;}catch(e){alert(无法获取摄像头/麦克风权限: e.message);setStatus(❌ 获取媒体设备失败,#f44336);throwe;}}​创建点对点连接绑定onicecandidate事件当本地产生ICE候选时发送给远端// 2. 创建连接pccreatePeerConnection();isCallertrue;// ---------- 创建 PeerConnection ----------functioncreatePeerConnection(){constpcnewRTCPeerConnection(rtcConfig);// 当本地产生ICE候选时发送给远端pc.onicecandidate(event){if(event.candidate){sendMessage({type:candidate,candidate:event.candidate});}};// 当收到远端流时显示在远程视频标签pc.ontrack(event){if(event.streamsevent.streams[0]){remoteVideo.srcObjectevent.streams[0];}};// 将本地流添加到连接中if(localStream){localStream.getTracks().forEach(track{pc.addTrack(track,localStream);});}returnpc;}​创建SDP协商offeroffer创建完后先存储到本地**setLocalDescription**函数调用完成后**本地产生候选**执行 onicecandidate事件发送候选给远端// 3. 创建 Offer (SDP)constofferawaitpc.createOffer();awaitpc.setLocalDescription(offer);​发送offer到信令服务器然后发送给信令服务器将offer 的sdp属性// 4. 发送 Offer 给远端sendMessage({type:offer,sdp:offer.sdp,// 可以附带一些元信息用 type 区分即可});2.3信令服务器中候选转发到远端电脑端收到ICE候选将其添加到连接中functionconnectSignaling(){......ws.onmessageasync(event){constdataJSON.parse(awaitevent.data.text());console.log(收到信令:,data);// 根据消息类型处理switch(data.type){......casecandidate:// 收到ICE候选 → 添加到连接中if(pc){try{awaitpc.addIceCandidate(newRTCIceCandidate(data.candidate));}catch(e){console.warn(添加ICE候选失败:,e);}}break;default:break;}};......}2.4信令服务器中offer转发到远端电脑端​ **远端收到后开始接听**改变状态提示提示对方发起通话请点击接听functionconnectSignaling(){......ws.onmessageasync(event){constdataJSON.parse(awaitevent.data.text());console.log(收到信令:,data);// 根据消息类型处理switch(data.type){caseoffer:// 收到呼叫邀请 → 自动弹出接听提示或直接调用 handleOffer// 这里我们让用户点击「接听」按钮来触发setStatus( 对方发起通话请点击「接听」,#2196F3);// 把收到的 offer 存到全局供点击接听时使用window.pendingOfferdata;// 启用接听按钮answerBtn.disabledfalse;break;......}};......}​ **获取本地流**显示电脑端的视频流在本地// 1. 获取本地流if(!localStream){awaitgetLocalStream();}​创建点对点连接绑定onicecandidate事件当本地产生ICE候选时发送给远端// 2. 创建连接pccreatePeerConnection();isCallerfalse;// ---------- 创建 PeerConnection ----------functioncreatePeerConnection(){constpcnewRTCPeerConnection(rtcConfig);// 当本地产生ICE候选时发送给远端pc.onicecandidate(event){if(event.candidate){sendMessage({type:candidate,candidate:event.candidate});}};// 当收到远端流时显示在远程视频标签pc.ontrack(event){if(event.streamsevent.streams[0]){remoteVideo.srcObjectevent.streams[0];}};// 将本地流添加到连接中if(localStream){localStream.getTracks().forEach(track{pc.addTrack(track,localStream);});}returnpc;}​设置远程描述将对方的offer手机端设置到远程描述// 3. 设置远程描述 (对方的 Offer)constofferwindow.pendingOffer;awaitpc.setRemoteDescription(newRTCSessionDescription(offer));​创建SDP协商answeranswer创建完后先存储到本地setLocalDescription**函数调用完成后**本地产生候选执行onicecandidate事件发送候选给对方手机端// 4. 创建 Answerconstanswerawaitpc.createAnswer();awaitpc.setLocalDescription(answer);​发送answer到信令服务器然后将answer 的sdp属性发送给信令服务器// 5. 发送 Answer 给远端sendMessage({type:answer,sdp:answer.sdp,});2.5信令服务器中候选转发到对方手机端收到ICE候选将其添加到连接中functionconnectSignaling(){......ws.onmessageasync(event){constdataJSON.parse(awaitevent.data.text());console.log(收到信令:,data);// 根据消息类型处理switch(data.type){......casecandidate:// 收到ICE候选 → 添加到连接中if(pc){try{awaitpc.addIceCandidate(newRTCIceCandidate(data.candidate));}catch(e){console.warn(添加ICE候选失败:,e);}}break;default:break;}};......}2.6信令服务器中answer转发到对方手机端手机端收到后​ 设置answer到远程描述通话建立functionconnectSignaling(){wsnewWebSocket(SIGNALING_SERVER_URL);ws.onopen(){setStatus(✅ 已连接到信令服务器等待对方...,#4CAF50);// 启用发起和接听按钮接听需要等对方发起才真正可用这里仅让界面可操作startBtn.disabledfalse;answerBtn.disabledfalse;// 但此时接听按钮按下其实还没用因为没有 offer后续逻辑会处理};ws.onmessageasync(event){constdataJSON.parse(awaitevent.data.text());console.log(收到信令:,data);// 根据消息类型处理switch(data.type){......caseanswer:// 收到应答 → 设置远程描述if(pc){awaitpc.setRemoteDescription(newRTCSessionDescription(data));setStatus( 通话已建立,#4CAF50);hangupBtn.disabledfalse;startBtn.disabledtrue;answerBtn.disabledtrue;}break;......}};......}2.7只有双方最终都完成了setRemoteDescription和addIceCandidate通话才最终建立
延伸阅读

更多相关文章

2026/9/6 19:46:12

Git 进阶实战:版本回滚、变基、cherry-pick 与冲突解决完全指南

一、版本回滚:reset 与 revert 的抉择版本回滚是日常开发中最常见的“后悔药”需求。不管是刚写完的提交发现有低级Bug,还是误把未完成的代码提交到了本地分支,甚至是不小心把带敏感信息的文件推到了远程,几乎每个开发者都曾遇到过…

2026/9/8 19:25:44

从抖音李要得到视频号罗杰:打车赴藏浪潮

从抖音李要得到视频号罗杰:打车赴藏浪潮 开篇引子 今年夏天,有两件小事串联起全网情绪:重庆小伙李要得打车两千多公里孤身进藏,在抖音引爆全民热血;时隔不久,导演罗杰术后携父母跨越三千多公里西行拉萨&…

2026/9/10 3:56:22

GESP六级完全二叉树:数组存储公式与代码模板全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/10 3:56:22

PowerToys Run 接入知乎直答:Windows 下的秒开 AI 知识搜索

1. 为什么把知乎直答塞进 PowerToys Run 先说我日常的一个高频动作:工作间隙卡住一个词条,想快速搜一下“这个概念的来源是什么”“这个说法有没有可靠出处”,然后决定要不要用在工作里。这个动作如果走标准流程,是先打开浏览器、…

2026/9/10 3:56:22

终极指南:基于插件的Telegram Bot神器telegram-bot完全解析

终极指南:基于插件的Telegram Bot神器telegram-bot完全解析 想要快速构建功能丰富的Telegram机器人吗?telegram-bot正是你需要的插件化机器人框架解决方案!这个基于Lua开发的Telegram Bot神器通过插件系统让机器人开发变得前所未有的简单。无…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码