vue项目基于WebRTC实现一对一音视频通话

效果

前端代码

<template><div class="flex items-center flex-col text-center p-12 h-screen"><div class="relative h-full mb-4 fBox"><video id="localVideo"></video><video id="remoteVideo"></video><div v-if="caller && calling"><p class="mb-4 text-white">等待对方接听...</p><img style="width: 60px;" @click="hangUp" src="@/assets/guaDuang.png" alt=""></div><div v-if="called && calling"><p>收到视频邀请...</p><div class="flex"><img style="width: 60px" @click="hangUp" src="@/assets/guaDuang.png" alt=""><img style="width: 60px" @click="acceptCall" src="@/assets/jieTing.png" alt=""></div></div></div><div><button @click="callRemote" style="margin-right: 10px">发起视频</button><button @click="hangUp" style="margin-left: 10px">挂断视频</button></div></div>
</template>
<script>
import { io, Socket } from "socket.io-client";
let roomId = '001';
export default {name: 'HelloWorld',props: {msg: String},data(){return{wsSocket:null,//实例called:false,// 是否是接收方caller:false,// 是否是发起方calling:false,// 呼叫中communicating:false,// 视频通话中localVideo:null,// video标签实例,播放本人的视频remoteVideo:null,// video标签实例,播放对方的视频peer:null,localStream:null,}},methods:{// 发起方发起视频请求async callRemote(){let that = this;console.log('发起视频');that.caller = true;that.calling = true;// await getLocalStream()// 向信令服务器发送发起请求的事件await that.getLocalStream();that.wsSocket.emit('callRemote', roomId)},// 接收方同意视频请求acceptCall(){console.log('同意视频邀请');this.wsSocket.emit('acceptCall', roomId)},// 挂断视频hangUp(){this.wsSocket.emit('hangUp', roomId)},reset(){this.called = false;this.caller = false;this.calling = false;this.communicating = false;this.peer = null;this.localVideo.srcObject = null;this.remoteVideo.srcObject = null;this.localStream = undefined;console.log('挂断结束视频-------')},// 获取本地音视频流async getLocalStream(){let that = this;let obj = { audio: true, video: true };const stream = await navigator.mediaDevices.getUserMedia(obj); // 获取音视频流that.localVideo.srcObject = stream;that.localVideo.play();that.localStream = stream;return stream;}},mounted() {let that = this;that.$nextTick(()=>{that.localVideo = document.getElementById('localVideo');that.remoteVideo = document.getElementById('remoteVideo');})let sock = io('localhost:3000'); // 对应服务的端口// 连接成功sock.on('connectionSuccess', (sock) => {console.log('连接成功:');});sock.emit('joinRoom', roomId) // 前端发送加入房间事件sock.on('callRemote', (sock) => {// 如果是发送方自己收到这个事件就不用管if (!that.caller){ // 不是发送方(用户A)that.called = true; // 接听方that.calling = true; // 视频通话中}});sock.on('acceptCall',async ()=>{if (that.caller){// 用户A收到用户B同意视频的请求that.peer = new RTCPeerConnection();// 添加本地音视频流that.peer.addStream && that.peer.addStream(that.localStream);// 通过监听onicecandidate事件获取candidate信息that.peer.onicecandidate = (event) => {if (event.candidate) {console.log('用户A获取candidate信息', event.candidate);// 通过信令服务器发送candidate信息给用户Bsock.emit('sendCandidate', {roomId, candidate: event.candidate})}}// 接下来用户A和用户B就可以进行P2P通信流// 监听onaddstream来获取对方的音视频流that.peer.onaddstream = (event) => {console.log('用户A收到用户B的stream',event.stream);that.calling = false;that.communicating = true;that.remoteVideo.srcObject = event.stream;that.remoteVideo.play();}// 生成offerlet offer = await that.peer.createOffer({offerToReceiveAudio: 1,offerToReceiveVideo: 1})console.log('offer', offer);// 设置本地描述的offerawait that.peer.setLocalDescription(offer);// 通过信令服务器将offer发送给用户Bsock.emit('sendOffer', { offer, roomId })}})// 收到offersock.on('sendOffer',async (offer) => {if (that.called){ // 接收方 - 用户Bconsole.log('收到offer',offer);// 创建自己的RTCPeerConnectionthat.peer = new RTCPeerConnection();// 添加本地音视频流const stream = await that.getLocalStream();that.peer.addStream && that.peer.addStream(stream);// 通过监听onicecandidate事件获取candidate信息that.peer.onicecandidate = (event) => {if (event.candidate) {console.log('用户B获取candidate信息', event.candidate);// 通过信令服务器发送candidate信息给用户Asock.emit('sendCandidate', {roomId, candidate: event.candidate})}}// 接下来用户A和用户B就可以进行P2P通信流// 监听onaddstream来获取对方的音视频流that.peer.onaddstream = (event) => {console.log('用户B收到用户A的stream',event.stream);that.calling = false;that.communicating = true;that.remoteVideo.srcObject = event.stream;that.remoteVideo.play();}// 设置远端描述信息await that.peer.setRemoteDescription(offer);let answer = await that.peer.createAnswer();console.log('用户B生成answer',answer);await that.peer.setLocalDescription(answer);// 发送answer给信令服务器sock.emit('sendAnswer', { answer, roomId })}})// 用户A收到answersock.on('sendAnswer',async (answer) => {if (that.caller){ // 接收方 - 用户A   判断是否是发送方// console.log('用户A收到answer',answer);await that.peer.setRemoteDescription(answer);}})// 收到candidate信息sock.on('sendCandidate',async (candidate) => {console.log('收到candidate信息',candidate);// await that.peer.addIceCandidate(candidate) // 用户A和用户B分别收到candidate后,都添加到自己的peer对象上// await that.peer.addCandidate(candidate)await that.peer.addIceCandidate(candidate)})// 挂断sock.on('hangUp',()=>{that.reset()})that.wsSocket = sock;}
}
</script>

服务端代码

const socket = require('socket.io');
const http = require('http');const server = http.createServer()const io = socket(server, {cors: {origin: '*' // 配置跨域}
});io.on('connection', sock => {console.log('连接成功...')// 向客户端发送连接成功的消息sock.emit('connectionSuccess');sock.on('joinRoom',(roomId)=>{sock.join(roomId);console.log('joinRoom-房间ID:'+roomId);})// 广播有人加入到房间sock.on('callRemote',(roomId)=>{io.to(roomId).emit('callRemote')})// 广播同意接听视频sock.on('acceptCall',(roomId)=>{io.to(roomId).emit('acceptCall')})// 接收offersock.on('sendOffer',({offer,roomId})=>{io.to(roomId).emit('sendOffer',offer)})// 接收answersock.on('sendAnswer',({answer,roomId})=>{io.to(roomId).emit('sendAnswer',answer)})// 收到candidatesock.on('sendCandidate',({candidate,roomId})=>{io.to(roomId).emit('sendCandidate',candidate)})// 挂断结束视频sock.on('hangUp',(roomId)=>{io.to(roomId).emit('hangUp')})
})server.listen(3000, () => {console.log('服务器启动成功');
});

完整代码gitee地址: https://gitee.com/wade-nian/wdn-webrtc.git

参考文章:基于WebRTC实现音视频通话_npm create vite@latest webrtc-client -- --template-CSDN博客

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

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

相关文章

【MQTT】服务端、客户端工具使用记录

目录 一、服务端 1.1 下载 1.2 相关命令 &#xff08;1&#xff09;启动 &#xff08;2&#xff09;关闭 &#xff08;3&#xff09;修改用户名和密码 1.3 后台管理 &#xff08;1&#xff09;MQTT配置 &#xff08;2&#xff09;集群概览 &#xff08;3&#xff09;…

如何使用SkyWalking收集分析分布式系统的追踪数据

Apache SkyWalking 是一个开源的观测性工具&#xff0c;用于收集、分析和展示分布式系统的追踪数据。SkyWalking 支持多种语言的追踪&#xff0c;包括但不限于 Java、.NET、Node.js 等。以下是使用 SkyWalking 工具实现数据采集的详细步骤&#xff1a; 1. 下载和安装 SkyWalkin…

数据挖掘算法原理与实践:决策树

第2关&#xff1a;决策树算法原理 任务描述 本关任务&#xff1a;根据本关所学知识&#xff0c;完成 calcInfoGain 函数。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 信息熵&#xff1b;条件熵&#xff1b;信息增益。 信息熵 信息是个很抽象的概念。…

风电厂数字孪生3D数据可视化交互展示构筑智慧化电厂管理体系

随着智慧电厂成为未来电力企业发展的必然趋势&#xff0c;深圳华锐视点紧跟时代步伐&#xff0c;引领技术革新&#xff0c;推出了能源3D可视化智慧管理系统。该系统以企业现有的数字化、信息化建设为基础&#xff0c;融合云平台、大数据、物联网、移动互联、机器人、VR虚拟现实…

Android 右键 new AIDL 无法选择

提示 (AIDL File)Requires setting the buildFeatures.aidl to true in the build file&#xff09; 解决方式&#xff1a; 在app的build.gradl中 adnroid{} 添加&#xff1a; buildFeatures{aidl true}

Oracle-一次TX行锁堵塞事件

问题背景&#xff1a; 接用户问题报障&#xff0c;应用服务出现大量会话堆积现象&#xff0c;数据库锁堵塞严重&#xff0c;需要协助进行问题定位和排除。 问题分析&#xff1a; 登录到数据库服务器上&#xff0c;首先查看一下数据库当前的等待事件情况&#xff0c;通过gv$ses…

教你零成本,免费使用comfyui复现remini爆火的黏土风格转绘(附完整教程)

在五一假期期间,一款名为Remini的AI照片编辑软件在小红书上迅速走红,其独特的“丑萌”黏土风格滤镜深受广大博主和用户的喜爱,引发了一波热潮,让人们玩得不亦乐乎。 Remini软件提供的这种视觉效果虽然看起来有点“丑萌”特效,然而,正是这种独树一帜的画风,使得Remini迅速…

【Django学习笔记(十)】Django的创建与运行

Django的创建与运行 前言正文1、安装Django2、创建项目2.1 基于终端创建项目2.2 基于Pycharm创建项目2.3 两种方式对比 3、默认项目文件介绍4、APP5、启动运行Django5.1 激活App5.2 编写URL和视图函数对应关系5.3 启动Django项目5.3.1 命令行启动5.3.2 Pycharm启动5.3.3 views.…

C++缺省参数、函数重载、引用

一、缺省参数 1.1缺省参数概念 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 void func(int n 0) {cout << n << endl; }int main() {func();func…

【经验01】spark执行离线任务的一些坑

项目背景: 目前使用spark跑大体量的数据,效率还是挺高的,机器多,120多台的hadoop集群,还是相当的给力的。数据大概有10T的量。 最近在出月报数据的时候发现有一个任务节点一直跑不过去,已经超过失败次数的阈值,报警了。 预警很让人头疼,不能上班摸鱼了。 经过分析发现…

Apache Knox 2.0.0使用

目录 介绍 使用 gateway-site.xml users.ldif my_hdfs.xml my_yarn.xml 其它 介绍 The Apache Knox Gateway is a system that provides a single point of authentication and access for Apache Hadoop services in a cluster. The goal is to simplify Hadoop securit…

LANCET:常见统计使用错误+规避建议!

国际顶级医学期刊《柳叶刀》&#xff08;The Lancet&#xff09;最近发表了一篇实用的通讯文章&#xff08;Correspondence&#xff09;&#xff0c;该篇Correspondence基于过去3年内对提交给《柳叶刀》的1000多篇manuscripts进行审核的经验&#xff0c;总结了科研report中常见…

【系统架构师】-UML-用例图(Use Case)

1、概述 用于表示系统功能需求&#xff0c;以及应用程序与用户或者与其他应用程序之间的交互关系。 2、组成 参与者&#xff08;Actors&#xff09;&#xff1a;与系统交互的用户或其他系统。用一个人形图标表示。用例&#xff08;Use Cases&#xff09;&#xff1a;系统需要…

2024年第九届数维杯数学建模A题思路分享

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

汇凯金业:黄金价格波动的原因是什么

黄金价格波动的原因通常是多方面的&#xff0c;包括但不限于&#xff1a; 经济数据&#xff1a;比如就业数据、通胀率、GDP增长率等对经济状况的指标不及预期&#xff0c;可能会增加黄金作为避险资产的吸引力。 货币政策&#xff1a;央行的利率决策、货币供应量的变化、量化宽…

当前主机使用的磁盘以及带宽情况

今日看到有用户在论坛留言反馈他买了Hostease Linux虚拟主机&#xff0c;想要查看当前主机使用的磁盘以及带宽情况&#xff0c;但是不知道如何查看。因为这边也是对于Hostease的虚拟主机产品是有所了解的&#xff0c;知道他们都是默认带管理面板的操做起来很方便的&#xff0c;…

基于FPGA的数字信号处理(10)--定点数的舍入模式(1)四舍五入round

1、前言 将浮点数定量化为定点数时&#xff0c;有一个避不开的问题&#xff1a;某些小数是无法用有限个数的2进制数来表示的。比如&#xff1a; 0.5(D) 0.1(B) 0.1(D) 0.0001100110011001~~~~(B) 可以看到0.5是可以精准表示的&#xff0c;但是0.1却不行。原因是整数是离散的…

Linux学习笔记2---Makefile

单个文件编译用gcc编译确实是挺方便的&#xff0c;但是多个文件需要编译一个个的编译就属实是麻烦了&#xff0c;而针对多文件编译也有快捷的办法&#xff0c;即Makefile脚本。要运行Makefile需要先安装make程序。 apt install make 1.什么是Makefile 一个工程中的源文件不计…

element-plus el-cascader 懒加载实现-省市区街道选择及回显

大概思路&#xff1a; 准备一个接口可以通过父Id,查询到下一级省市区街道的信息&#xff1b;如下方的getRegionListOne确定后端的数据结构&#xff0c;需要在created里边处理数据回显逻辑el-cascader接收的数据格式是[‘’,‘’,‘’];后端的数据格式多为[{provinceId: ‘’, …

基于随机森林与支持向量机的高光谱图像分类(含python代码)

目录 一、背景 二、代码实现 三、项目代码 一、背景 基于深度学习的教程&#xff08;卷积神经网络&#xff09;详见&#xff1a;基于卷积神经网络的高光谱图像分类详细教程&#xff08;含python代码&#xff09;-CSDN博客 在高光谱图像分类领域&#xff0c;随机森林&#…