WebRTC 双向视频通话

WebRTC 双向视频通话

一、项目概述

WebRTC(Web Real - Time Communication)是一种支持浏览器之间进行实时通信的技术,它使得在网页上实现音视频通话、文件共享等功能变得更加容易。为了体验这个技术,所以我实现了webrtc - local

二、项目结构

项目主要分为两个主要部分:webrtc - server(服务端)和 webrtc - client(客户端)。

1. 服务端(webrtc - server)

服务端使用 Node.js 搭建,借助 socket.io (也可以是其他)实现信令的一个交换,其实就是一个信息中转的地方。

2. 客户端(webrtc - client)

客户端基于 Vue 3 和 TypeScript 构建,使用 socket.io - client 与服务端进行通信。

三、项目启动步骤

详看 README

四、代码实现分析

1. 服务端(index.js)

服务端使用 https 协议创建服务器,并使用 socket.io 处理实时通信。以下是主要逻辑:

const socket = require('socket.io')
const https = require('https')
const fs = require('fs')
const path = require('path')const server = https.createServer({key: fs.readFileSync(path.join(__dirname, '../cert/key.pem')),cert: fs.readFileSync(path.join(__dirname, '../cert/cert.pem')),
})const io = socket(server, {cors: {origin: '*', // 配置跨域},
})io.on('connection', (sock) => {console.log('连接成功...')sock.emit('connectionSuccess')// 监听客户端进入房间的事件sock.on('joinRoom', (roomId) => {sock.join(roomId)})// 处理各种视频通话相关事件sock.on('callRemote', (roomId) => {io.to(roomId).emit('receiveCall')})sock.on('acceptCall', (roomId) => {io.to(roomId).emit('acceptCall')})// 处理 offer、answer 和 candidate 信息sock.on('sendOffer', ({ roomId, offer }) => {io.to(roomId).emit('sendOffer', offer)})sock.on('sendAnswer', ({ roomId, answer }) => {io.to(roomId).emit('receiveAnswer', answer)})sock.on('sendCandidate', ({ roomId, candidate }) => {io.to(roomId).emit('receiveCandidate', candidate)})sock.on('hangUp', (roomId) => {io.to(roomId).emit('hangUp')})
})server.listen(3001, () => {console.log('服务器启动成功')
})

服务端主要负责监听客户端的连接和各种事件,用来交换不同客户端的信令等数据。

2. 客户端(App.vue)

客户端使用 Vue 3,结合 socket.io - clientRTCPeerConnection 实现视频通话。以下是主要逻辑及相关知识解释:

4.2.1 获取本地音视频流
// 获取本地音视频流
const getLocalStream = async () => {// 获取音视频流const stream = await navigator.mediaDevices.getUserMedia({video: true,audio: true,})// 将媒体流设置到 video 标签上播放localVideo.value!.srcObject = stream// 播放音视频流localVideo.value!.play()// 存储本地流localStream.value = streamreturn stream
}

知识解释
navigator.mediaDevices.getUserMedia 是 WebRTC 提供的一个 API,用于请求访问用户的摄像头和麦克风。它接受一个约束对象作为参数,该对象指定了需要获取的媒体类型(如视频、音频)以及其他可选的配置。当用户允许访问后,该方法会返回一个 Promise,该 Promise 会解析为一个 MediaStream 对象,该对象包含了用户的音视频流。

4.2.2 处理视频请求
// 发起视频请求(发起方)
const callRemote = async () => {if (calling.value || communicating.value) {return}calling.value = true// 获取本地音视频流await getLocalStream()// 向服务器发送发起视频请求的事件caller.value = truesocket.value.emit('callRemote', roomId)
}// 接收视频请求(接受方)
const acceptCall = () => {// 向服务器发送接受视频请求的事件socket.value.emit('acceptCall', roomId)
}

知识解释
在 WebRTC 视频通话中,发起方首先需要获取本地音视频流,然后通过 socket.io 向服务端发送视频请求。服务端接收到请求后,将其广播给房间内的其他客户端。接收方接收到请求后,可以选择接受或拒绝。如果接受,接收方会向服务端发送接受请求的事件,服务端再将该事件广播给发起方。

4.2.3 交换 offer/answer
// 发送方收到同意视频事件
sock.on('acceptCall', async () => {if (caller.value) {// 发送方// 创建RTCPeerConnection对象peer.value = new RTCPeerConnection()// 添加本地音视频流peer.value.addStream(localStream.value)// 生成offerconst offer = await peer.value.createOffer({offerToReceiveAudio: true,offerToReceiveVideo: true,})// 设置本地描述的offerawait peer.value.setLocalDescription(offer)// 发送offersock.emit('sendOffer', { roomId, offer })}
})// 接收方收到offer
sock.on('sendOffer', async (offer: any) => {if (called.value) {const stream = await getLocalStream()// 接收方创建自己的RTCPeerConnection对象peer.value = new RTCPeerConnection()// 添加本地音视频流peer.value.addStream(stream)// 设置远端描述信息await peer.value.setRemoteDescription(offer)// 生成answerconst answer = await peer.value.createAnswer()// 设置本地描述信息await peer.value.setLocalDescription(answer)// 发送answersock.emit('sendAnswer', { roomId, answer })}
})// 发送方收到接收方的answer
sock.on('receiveAnswer', (answer: any) => {if (caller.value) {// 设置远端描述信息peer.value.setRemoteDescription(answer)}
})

知识解释

  • offer:发起方通过 RTCPeerConnection.createOffer 方法创建一个 offer,该 offer 包含了发起方的会话描述信息,如支持的编解码器、媒体类型等。然后使用 RTCPeerConnection.setLocalDescription 方法将该 offer 设置为本地描述,并通过 socket.io 发送给接收方。
  • answer:接收方收到 offer 后,使用 RTCPeerConnection.setRemoteDescription 方法设置远端描述,然后通过 RTCPeerConnection.createAnswer 方法创建一个 answer,该 answer 包含了接收方的会话描述信息。同样,使用 RTCPeerConnection.setLocalDescription 方法将该 answer 设置为本地描述,并通过 socket.io 发送给发起方。
  • 会话描述协议(SDP)offeranswer 都是基于会话描述协议(SDP)的,SDP 是一种用于描述多媒体会话的格式,它包含了会话的各种信息,如媒体类型、编解码器、传输地址等。通过交换 offeranswer,双方可以协商出一个共同支持的会话配置。
4.2.4 交换 candidate 信息
// 获取candidate信息
peer.value.onicecandidate = (event: any) => {if (event.candidate) {// 向服务器发送candidate信息sock.emit('sendCandidate', { roomId, candidate: event.candidate })}
}// 接收candidate信息
sock.on('receiveCandidate', async (candidate: any) => {await peer.value.addIceCandidate(candidate)
})

知识解释

  • ICE(交互式连接建立):由于双方可能位于不同的网络环境中,需要通过 ICE 机制来找到双方之间的最佳通信路径。ICE 会收集双方的网络地址信息,这些信息被称为 candidate
  • candidatecandidate 包含了设备的网络地址和端口信息,可能是本地地址、反射地址(通过 NAT 获得)或中继地址(通过 TURN 服务器获得)。当 RTCPeerConnection 对象收集到一个 candidate 时,会触发 onicecandidate 事件,此时可以将该 candidate 通过 socket.io 发送给对方。
  • addIceCandidate:对方接收到 candidate 后,使用 RTCPeerConnection.addIceCandidate 方法将其添加到自己的 RTCPeerConnection 对象中,这样双方就可以尝试通过该 candidate 建立连接。

五、总结

webrtc - local 项目通过结合 WebRTC 技术和 socket.io 实现了简单的局域双向视频通话功能,客户端和服务端在同一网络环境下运行。
在实际应用中,可以考虑使用 STUN/TURN 服务器来解决跨网络通信的问题。

仓库

https://github.com/PL-FE/webrtc-local/tree/main

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87794.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Paimon lookup核心过程:分级查找、二分和缓存创建

LookupLevels LookupLevels 在 Paimon 中扮演着**“带缓存的、基于 Key 的数据查找引擎”**的角色。它的核心使命是:当需要根据主键(Key)查找某条数据时,能够高效地在 LSM-Tree 的多层(Levels)数据文件中定…

Ruby大会演讲实录:Baklib 如何用 AI 重构内容管理赛道

“2015 年成都 Ruby 大会时,我们还在做大数据项目;2025 年的今天,Baklib 已服务 800 多家企业。” 在 RubyConf China 2025 的演讲台上,Baklib 创始人Song以十年对比开篇,讲述了从技术爱好者到企业服务创业者的蜕变&am…

408第三季part2 - 计算机网络 - 传输层II

理解第一次和第二次握手是不能携带数据,只能消耗一个序号后面挥手也有第一次和第三次题目建立连接是1000,FIN挥手是5001,这两个是不会带数据的所以字节数范围是1001-50005000-10011 4000c再次理解还可以叫快速重传题目服务器想要100确认号客…

揭秘图像LLM:从像素到语言的智能转换

图像LLM是怎么工作 图像LLM(多模态大语言模型)的核心是将图像转化为语言模型能理解的“语言”,并与文本深度融合。以下结合CLIP、DALL-E、GPT-4V等主流模型,通过具体例子说明其工作机制: 一、图像→特征向量:从像素到“密码” 例子:识别“戴墨镜的猫” 视觉编码器提取…

十、K8s集群资源合理化分配

十、K8s集群资源合理化分配 文章目录 十、K8s集群资源合理化分配1、K8s 资源限制 ResourceQuota1.1 什么是ResourceQuota?1.2 ResourceQuota通常用于如下场景:1.3 基于租户和团队的资源限制1.4 基于命名空间的资源限制 2、K8s 资源限制 LimitRange2.1 设…

Android 13 设置界面会判断当前屏幕的大小,如果是大屏,则为左右屏显示

1.前言 在13.0的系统rom定制化开发中,在某些时候,在大屏设备中,设置新增了左右分屏的功能,就是 左边显示主菜单,右边显示一级菜单的功能,某些情况下不需要,接下来关闭这个功能 2.设置界面会判断当前屏幕的大小,如果是大屏,则为左右屏显示的核心类 packages/apps/Sett…

算法学习day15----蓝桥杯--进制转换

python代码:def jinzhizhuanhuan(x, n):ans 0while x > 0:ans x % nx // nreturn ans sum 0 for i in range(1, 2025):if jinzhizhuanhuan(i, 2) jinzhizhuanhuan(i, 4):sum 1 print(sum)虽然结束了期末周,但是又变懒了不少,水一个先…

OpenStack扩展

一、OpenStack (开源云计算管理平台)的核心服务组件及功能计算服务(Compute - Nova)功能:是 OpenStack 计算资源管理的核心组件,负责虚拟机实例全生命周期管理,像实例的创建、启动、暂停、迁移、…

【LeetCode 热题 100】41. 缺失的第一个正数——(解法二)原地哈希

Problem: 41. 缺失的第一个正数 题目:给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 【LeetCode 热题 100】41. 缺失的第一个正数——(解法一&am…

C#上位机之Modbus通信协议!

文章目录前言一、Modbus概念二、使用步骤1.使用Modbus准备2.使用步骤三、Modbus RTU 与 Modbus ASCII对比前言 Modbus通信协议! 一、Modbus概念 从站设备编码(从站地址、单元ID),一主多从。 存储区:0-线圈状态、1-输…

前后端分离架构下的跨域问题与解决方案

在现代Web开发中,特别是随着前后端分离架构的普及,跨域问题成为了开发者必须面对的一个重要议题。本文将详细介绍什么是跨域问题、其产生的原因以及如何从前端和后端两个角度来解决这个问题,并提供一些实用的代码示例。一、跨域问题概述1. 定…

搜索数据建设系列之数据架构重构

导读 主要概述百度搜索业务数据建设的创新实践,重点围绕宽表模型设计、计算引擎优化和新一代业务服务交付模式(图灵3.0开发模式)三大方向,解决了传统数仓在搜索场景下面临的诸多挑战,实现了搜索数据建设的高效、稳定、…

2025年渗透测试面试题总结-2025年HW(护网面试) 29(题目+回答)

安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。、 目录 2025年HW(护网面试) 29 1. 样本分析思路 2. Linux GDB分析样本示例 3. 应急案例:WebShell后…

动态编程入门第二节:委托与事件 - Unity 开发者的高级回调与通信艺术

动态编程入门第一节:C# 反射 - Unity 开发者的超级工具箱 动态编程入门第二节:委托与事件 - Unity 开发者的高级回调与通信艺术 上次我们聊了 C# 反射,它让程序拥有了在运行时“看清自己”的能力。但光能看清还不够,我们还需要让…

降低网络安全中的人为风险:以人为本的路径

有效降低网络安全中的人为风险,关键在于采取以人为本的方法。这种方法的核心在于通过高效的培训和实践,使员工掌握安全知识、践行安全行为,并最终培育出安全且相互支持的文化氛围。 诚然,技术和政策必须为良好的安全行为提供支持、…

opencv裁剪和编译

opencv裁剪和编译 0. 准备工作 0.1 下载和安装Eigen 地址 https://eigen.tuxfamily.org/index.php?titleMain_Page对于opencv编译,需要增加EIGEN_INCLUDE_PATH和开启WITH_EIGEN -DWITH_EIGENON -DEIGEN_INCLUDE_PATH./3rd/eigen-3.4.01. 实际脚本 编译脚本如下: ch…

小白成长之路-mysql数据基础(三)

文章目录一、主从复制二、案例总结一、主从复制 1、master开启二进制日志记录2、slave开启IO进程,从master中读取二进制日志并写入slave的中继日志3、slave开启SQL进程,从中继日志中读取二进制日志并进行重放4、最终,达到slave与master中数据…

通过 Windows 共享文件夹 + 手机访问(SMB协议)如何实现

通过 Windows 共享文件夹 手机访问(SMB协议) 实现 PC 和安卓手机局域网文件共享,具体步骤如下: 📌 前置条件 电脑和手机连接同一局域网(同一个Wi-Fi或路由器)。关闭防火墙或放行SMB端口&#…

【Python3教程】Python3高级篇之正则表达式

博主介绍:✌全网粉丝23W+,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物联网、机器学习等设计与开发。 感兴趣的可…

Redis--黑马点评--达人探店功能实现详解

达人探店发布探店笔记探店笔记类似于点评网站的评价,往往是图文结合,对应的表有两个:tb_blog:探店笔记表,包含笔记中的标题、文字、图片等tb_blog_comments:其他用户对探店笔记的评价tb_blog表结构如下&…