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信息给用户B
            sock.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();
        }


        // 生成offer
        let offer = await that.peer.createOffer({
          offerToReceiveAudio: 1,
          offerToReceiveVideo: 1
        })
        console.log('offer', offer);
        // 设置本地描述的offer
        await that.peer.setLocalDescription(offer);
        // 通过信令服务器将offer发送给用户B
        sock.emit('sendOffer', { offer, roomId })
      }
    })

    // 收到offer
    sock.on('sendOffer',async (offer) => {
      if (that.called){ // 接收方 - 用户B
        console.log('收到offer',offer);
        // 创建自己的RTCPeerConnection
        that.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信息给用户A
            sock.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收到answer
    sock.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')
    })

    // 接收offer
    sock.on('sendOffer',({offer,roomId})=>{
        io.to(roomId).emit('sendOffer',offer)
    })

    // 接收answer
    sock.on('sendAnswer',({answer,roomId})=>{
        io.to(roomId).emit('sendAnswer',answer)
    })

    // 收到candidate
    sock.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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深圳车间厂房降温用什么设备好?

环保水空调&#xff08;也被称为水冷空调或蒸发式降温换气机组&#xff09;的特点主要体现在以下几个方面&#xff1a; 节能环保&#xff1a;环保水空调使用水作为冷媒介&#xff0c;相比传统空调的制冷方式&#xff0c;它能在制冷过程中节约更多的能源&#xff0c;减少碳排放…

羊大师分析,为什么羊奶是孩子的理想饮品?

羊大师分析&#xff0c;为什么羊奶是孩子的理想饮品&#xff1f; 羊奶&#xff0c;作为一种传统的营养饮品&#xff0c;近年来逐渐受到家长们的青睐&#xff0c;成为孩子们的理想饮品。那么&#xff0c;羊大师将为大家讲解&#xff0c;为什么羊奶能够赢得如此多的赞誉&#xf…

Beego 使用教程 5:页面视图

beego 是一个用于Go编程语言的开源、高性能的 web 框架 beego 被用于在Go语言中企业应用程序的快速开发&#xff0c;包括RESTful API、web应用程序和后端服务。它的灵感来源于Tornado&#xff0c; Sinatra 和 Flask beego 官网&#xff1a;http://beego.gocn.vip/ 上面的 bee…

如何备考PMP才能一次通过?

PMP备考一个月就能通过&#xff0c;培训机构中就应该这么学&#xff01; PMP考试的难度其实并没有大家想象中的那么大&#xff0c;现在培训机构的通过率基本也在90%以上&#xff0c;而这90%以上也基本都是头一次参加考试很少有参加重考的学员。我就是在威班PMP培训了一个多月一…

「Java开发指南」如何用MyEclipse搭建GWT 2.1和Spring?(一)

本教程将指导您如何生成一个可运行的Google Web Toolkit (GWT) 2.1和Spring应用程序&#xff0c;该应用程序为域模型实现了CRUD应用程序模式。在本教程中&#xff0c;您将学习如何&#xff1a; 安装Google Eclipse插件为GWT配置一个项目搭建从数据库表到一个现有的项目GWT编译…

net7部署经历

1、linux安装dotnet命令&#xff1a; sudo yum install dotnet-sdk-7.0 或者直接在商店里安装 2、配置反向代理 127.0.0.1:5000》localhost 访问后报错 原因&#xff1a;数据表驼峰名&#xff0c; 在windows的数据表不区分大小写&#xff0c;但是在linux里面是默认区分的&…

【数据结构】基于顺序表实现通讯录

世界会向那些有目标和远见的人让路。&#x1f493;&#x1f493;&#x1f493; 目录 •&#x1f319;说在前面 &#x1f34b;基于顺序表的通讯录 • &#x1f330;1.技术要点 • &#x1f330;2.通讯录流程设计 &#x1f34b;通讯录基本量设计 • &#x1f330;1.定义联系…

PLM系统推荐:产品全生命周期管理最佳解决方案

PLM系统推荐&#xff1a;产品全生命周期管理最佳解决方案 在当今日益竞争激烈的市场环境中&#xff0c;企业如何高效管理其产品设计、开发和生命周期变得尤为重要。产品生命周期管理&#xff08;PLM&#xff09;系统正是为解决这一难题而诞生的。本文将为您详细介绍几款值得推荐…

HTTP免费升级到HTTPS攻略

HTTPS就是在HTTP的基础上加入了SSL&#xff0c;将一个使用HTTP的网站免费升级到HTTPS的关键就是申请一个免费的SSL证书 具体步骤如下 1 获取免费SSL证书 国内的JoySSL 提供不限量免费的SSL/TLS证书。根据自己的需求选择证书类型&#xff08;登录JoySSL官网&#xff0c;创建账号…

5.10开幕!虚拟动力多项数字人互动技术参展元宇宙生态博览会!

2024年5月10-12日&#xff0c;由广东鸿威国际会展集团有限公司、广州市虚拟现实行业协会主办的2024数字显示与元宇宙生态博览会将正式开幕。 亮点抢先看 虚拟动力 广州虚拟动力作为3D虚拟人全生态应用的产品技术开发与服务商&#xff0c;将携带无穿戴动捕技术、数字人穿戴式动…

【C++】Visual Studio 2019 给 C++ 文件添加头部注释说明

使用代码片段管理器&#xff0c;添加快捷插入代码文件说明 1. 效果 2. header.snippet 新建 header.snippet 文件&#xff0c;存放到某个文件夹 内容&#xff0c;自行更新 快捷名称&#xff0c;修改 Header 里面内容注释内容&#xff0c;修改 Code 里面内容 <?xml ver…

Linux中每当执行‘mount’命令(或其他命令)时,自动激活执行脚本:输入密码,才可以执行mount

要实现这个功能&#xff0c;可以通过创建一个自定义的mount命令的包装器&#xff08;wrapper&#xff09;来完成。这个包装器脚本会首先提示用户输入密码&#xff0c;如果密码正确&#xff0c;则执行实际的mount命令。以下是创建这样一个包装器的步骤&#xff1a; 创建一个名为…

Vue从入门到实战Day01

一、Vue快速上手 1. vue概念 概念&#xff1a;Vue是一个用于 构建用户界面的 渐进式 框架 构建用户界面&#xff1a;基于数据动态渲染页面渐进式&#xff1a;循序渐进的学习框架&#xff1a;一套完整的项目解决方案&#xff0c;提升开发效率 优点&#xff1a;大大提升开发效…

Garden Planner for Mac v3.8.62注册激活版:园林绿化设计软件

Garden Planner for Mac是一款专为苹果Mac OS平台设计的园林景观设计软件。这款软件的主要功能是帮助用户设计梦想中的花园&#xff0c;包括安排植物、树木、建筑物和其他物体。 Garden Planner for Mac提供了一个包含1200多种植物和物体符号的库&#xff0c;这些符号都可以进行…

torch教程

一 基本用法 1 torch.autograd.Function PyTorch 74.自定义操作torch.autograd.Function - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/344802526 虽然pytorch可以自动求导,但是有时候一些操作是不可导的,这时候你需要自定义求导方式。也就是所谓的 "Extending t…

图纸管理的高效策略与最佳实践

图纸管理的高效策略与最佳实践 在工程设计、产品研发和建筑行业中&#xff0c;图纸管理是一项至关重要的任务。随着项目规模的扩大和复杂性的增加&#xff0c;如何高效、有序地管理图纸已成为企业和团队关注的焦点。本文将为您介绍图纸管理的高效策略与最佳实践&#xff0c;帮助…

【iOS逆向与安全】网上gw如何自动登录与签到SM2,SM3,SM4算法加解密

1.下载 app 2.frida 调试 3.抓包查看接口 4.分析加密数据 5.易语言编写代码 1 .开始下载 下载好发现有越狱检测&#xff0c;检测点为&#xff1a; -[AppDelegate isJailBreak]; 于是编写插件xm代码 : %hook AppDelegate- (void)isJailBreak{NSLog("AppDelegate is…

【JavaEE初阶系列】——Servlet运行原理以及Servlet API详解

目录 &#x1f6a9;Servlet运行原理 &#x1f6a9;Servlet API 详解 &#x1f393;HttpServlet核心方法 &#x1f393;HttpServletRequest核心方法 &#x1f388;核心方法的使用 &#x1f534;获取请求中的参数 &#x1f4bb;query string &#x1f4bb;直接通过form表…

免费思维13招之二:第三方思维

思维02:第三方思维 第三方思维又叫第三方资费思维。是一种可以使你的产品免费但是你却依然赚钱的思维。 大家还记得之前讲的“餐厅免费吃饭却年赚百万”的案例吗?这个案例运用了多种免费思维的子思维,其中也用到了第三方资费思维,怎么运用的呢?韩女士,与各行各业合作,…

电脑上如何设置闹钟提醒 电脑闹钟提醒设置方法

在这个信息爆炸的时代&#xff0c;我们每个人每天都面临着无数的任务和约定。繁杂的工作与生活&#xff0c;让我时常感到应接不暇&#xff0c;一不小心就会遗漏某些重要事务&#xff0c;这给我带来不小的困扰。我相信&#xff0c;很多人都有过这样的经历&#xff0c;面对一堆待…