WebSocket+Vue+SpringBoot实现语音通话

news/2024/4/19 14:12:36/文章来源:https://blog.csdn.net/qq_46122292/article/details/130326752

参考文章

  • 整体思路

前端点击开始对话按钮后,将监听麦克风,获取到当前的音频,将其装化为二进制数据,通过websocket发送到webscoket服务端,服务端在接收后,将消息写入给指定客户端,客户端拿到发送过来的二进制音频后再转化播放

  • 注意事项
    由于音频转化后的二进制数据较大,websocket默认的消息传输大小不能被接收,所以需要通过 @OnMessage(maxMessageSize=5242880)注解进行调整

  • Vue代码

<template><div class="play-audio"><el-button @click="startCall" ref="start">开始对讲</el-button><el-button @click="stopCall" ref="stop">结束对讲</el-button></div>
</template><script>
export default {data() {return {ws: null,mediaStack: null,audioCtx: null,scriptNode: null,source: null,play: true}},methods: {initWs1() {//设置好友IDlet recipientId=localStorage.getItem('userId')=="2"?"1":"2";this.ws = new WebSocket('ws://192.168.206.204:8081/video/'+localStorage.getItem('userId')+"/"+recipientId)this.ws.onopen = () => {console.log('socket 已连接')}this.ws.binaryType = 'arraybuffer'this.ws.onmessage = ({ data }) => {console.log("接收到的数据--》"+ data)// 将接收的数据转换成与传输过来的数据相同的Float32Arrayconst buffer = new Float32Array(data)// 创建一个空白的AudioBuffer对象,这里的4096跟发送方保持一致,48000是采样率const myArrayBuffer = this.audioCtx.createBuffer(1, 4096, 48000)// 也是由于只创建了一个音轨,可以直接取到0const nowBuffering = myArrayBuffer.getChannelData(0)// 通过循环,将接收过来的数据赋值给简单音频对象for (let i = 0; i < 4096; i++) {nowBuffering[i] = buffer[i]}// 使用AudioBufferSourceNode播放音频const source = this.audioCtx.createBufferSource()source.buffer = myArrayBufferconst gainNode = this.audioCtx.createGain()source.connect(gainNode)gainNode.connect(this.audioCtx.destination)var muteValue = 1if (!this.play) { // 是否静音muteValue = 0}gainNode.gain.setValueAtTime(muteValue, this.audioCtx.currentTime)source.start()}this.ws.onerror = (e) => {console.log('发生错误', e)}this.ws.onclose = () => {console.log('socket closed')}},// 开始对讲startCall() {this.play = truethis.audioCtx = new AudioContext()this.initWs1()// 该变量存储当前MediaStreamAudioSourceNode的引用// 可以通过它关闭麦克风停止音频传输// 创建一个ScriptProcessorNode 用于接收当前麦克风的音频this.scriptNode = this.audioCtx.createScriptProcessor(4096, 1, 1)navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then((stream) => {this.mediaStack = streamthis.source = this.audioCtx.createMediaStreamSource(stream)this.source.connect(this.scriptNode)this.scriptNode.connect(this.audioCtx.destination)}).catch(function (err) {/* 处理error */console.log('err', err)})// 当麦克风有声音输入时,会调用此事件// 实际上麦克风始终处于打开状态时,即使不说话,此事件也在一直调用this.scriptNode.onaudioprocess = (audioProcessingEvent) => {const inputBuffer = audioProcessingEvent.inputBuffer// console.log("inputBuffer",inputBuffer);// 由于只创建了一个音轨,这里只取第一个频道的数据const inputData = inputBuffer.getChannelData(0)console.log("调用")// 通过socket传输数据,实际上传输的是Float32Arrayif (this.ws.readyState === 1) {// console.log("发送的数据",inputData);this.ws.send(inputData)}}},// 关闭麦克风stopCall() {this.play = falsethis.mediaStack.getTracks()[0].stop()this.scriptNode.disconnect()}}
}
</script>

-java代码
webscoket配置类

package com.example.software.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @Description: websocket配置*/
@Configuration
@EnableWebSocket
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

webscoket服务类

package com.example.software.service.webscoket;import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @Author:wf* @Date 2023/5/14 13:55* 消息收发**/
@Controller
@ServerEndpoint(value = "/video/{senderID}/{recipientId}")
@Slf4j
public class WebSocketServer {/** 当前在线连接数。应该把它设计成线程安全的 */private static  int onlineCount = 0;/** 存放每个客户端对应的MyWebSocket对象。实现服务端与单一客户端通信的话,其中Key可以为用户标识 */private static ConcurrentHashMap<String, Session> webSocketSet = new ConcurrentHashMap<String, Session>();/** 与某个客户端的连接会话,需要通过它来给客户端发送数据 */private Session WebSocketsession;/** 当前发消息的人员编号 */private String senderID = "";/*** 连接建立成功调用的方法* @param param 发送者ID,是由谁发送的* @param WebSocketsession 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据*/@OnOpenpublic void onOpen(@PathParam(value = "senderID") String param, @PathParam(value = "recipientId") String recipientId,Session WebSocketsession) {System.out.println("人员-------**-------编号:"+param+":加入聊天");System.out.println("盆友是:"+recipientId+"");//接收到发送消息的人员编号senderID = param;System.out.println("senderID:"+senderID);//设置消息大小最大为10M,这种方式也可以达到效果,或者使用下面的    @OnMessage(maxMessageSize=5242880)//The default buffer size for text messages is 8192 bytes.消息超过8192b,自动断开连接//        WebSocketsession.setMaxTextMessageBufferSize(10*1024*1024);
//        WebSocketsession.setMaxBinaryMessageBufferSize(10*1024*1024);//加入map中,绑定当前用户和socketwebSocketSet.put(param, WebSocketsession);//在线数加1addOnlineCount();}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {if (StrUtil.isNotBlank(senderID)) {//从set中删除webSocketSet.remove(senderID);//在线数减1subOnlineCount();}}/*** 收到客户端消息后调用的方法**设置最大接收消息大小*/@OnMessage(maxMessageSize=5242880)public void onMessage(@PathParam(value = "senderID") String senderID ,@PathParam(value = "recipientId") String recipientId,InputStream inputStream) {System.out.println(senderID+":发送给"+recipientId+"的消息-->"+inputStream);try {byte[] buff = new byte[inputStream.available()];inputStream.read(buff, 0, inputStream.available());Session session = webSocketSet.get("2");synchronized (session) {//给2号发送session.getBasicRemote().sendBinary(ByteBuffer.wrap(buff));}} catch (Exception e) {e.printStackTrace();}}/*** 发生错误时调用** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();}/*** 为指定用户发送消息** @param message 消息内容* @throws IOException*/public void sendMessage(String message) throws IOException {//加同步锁,解决多线程下发送消息异常关闭synchronized (this.WebSocketsession){this.WebSocketsession.getBasicRemote().sendText(message);}}/*** 获取当前在线人数* @return 返回当前在线人数*/public static synchronized int getOnlineCount() {return onlineCount;}/*** 增加当前在线人数*/public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}/*** 减少当前在线人数*/public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}}
  • 测试方法
    1.使用两个浏览器模拟两个用户,首先在浏览器本地存储一个用户ID
    用户A–谷歌浏览器:
    在这里插入图片描述
    用户B–火狐浏览器
    在这里插入图片描述
    2.点击按钮,进行测试
    3.关于谷歌浏览器提示TypeError: Cannot read property ‘getUserMedia’ of undefined
    原因:chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限
    解决方案:
    1.网页使用https访问,服务端升级为https访问,配置ssl证书
    2.使用localhost或127.0.0.1 进行访问
    3.修改浏览器安全配置(最直接、简单)
    a.首先在chrome浏览器中输入如下指令
chrome://flags/#unsafely-treat-insecure-origin-as-secure 

在这里插入图片描述

b.然后开启 Insecure origins treated as secure
在下方输入栏内输入你访问的地址url,然后将右侧Disabled 改成 Enabled即可
在这里插入图片描述

c.然后浏览器会提示重启, 点击Relaunch即可
在这里插入图片描述

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

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

相关文章

日本PSE认证日本的電気用品安全法METI备案

日本的電気用品安全法&#xff08;PSE认证&#xff09;法规要求日本的采购商在购进商品后一个月内必须向日本METI注册申报&#xff0c;并必须将采购商名称或ID标在产品上&#xff0c;以便在今后产品销售过程中进行监督管理&#xff0c;完成后将获得電気用品製造事業届出書&…

Java基础学习(10)

Java基础学习 一、JDK8时间类1.1 Zoneld时区1.2 Instant时间戳1.3 ZonedDateTime1.4 DateTimeFormatter1.5 日历类时间表示1.6 工具类1.7 包装类JDK5提出的新特性Integer成员方法 二、集合进阶2.1 集合的体系结构2.1.1 Collection 2.2collection的遍历方式2.2.1 迭代器遍历2.2.…

元宇宙场景下的实时互动RTI技术能力构建

元宇宙可谓是处在风口浪尖&#xff0c;无数的厂商都对元宇宙未来抱有非常美好的憧憬。正因如此&#xff0c;许许多多厂商都在用他们自己的方案&#xff0c;为元宇宙更快、更好的实现&#xff0c;在自己的领域贡献力量。LiveVideoStack 2022北京站邀请到了 ZEGO 即构科技的解决方…

17.集合

集合 集合类是Java数据结构的实现。Java的集合类是java.util包中的重要内容&#xff0c;它允许以各种方式将元素分组&#xff0c;并定义了各种使这些元素更容易操作的方法。Java集合类是Java将一些基本的和使用频率极高的基础类进行封装和增强后再以一个类的形式提供。集合类是…

【Vue2源码】响应式原理

【Vue2源码】响应式原理 文章目录 【Vue2源码】响应式原理Vue响应式的核心设计思路整体流程响应式中的关键角色检测变化注意事项响应式原理数据观测重写数组7个变异方法增加__ob__属性__ob__有两大用处&#xff1a; Vue.js 基本上遵循 MVVM&#xff08;Model–View–ViewModel&…

【Cartopy基础入门】如何丝滑的加载Geojson数据

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 Cartopy基础入门 【Cartopy基础入门】Cartopy的安装 【Cartopy基础入门】如何丝滑的加载Geojson数据 文章目录 Cartopy基础入门一、Geojson数据来源二…

camunda的manual task节点用途

Camunda的Manual Task用于在流程中暂停执行&#xff0c;直到人工干预完成某个任务。与User Task不同&#xff0c;Manual Task没有分配给特定用户或用户组&#xff0c;而是需要手动启动并指定下一步流程。 Manual Task可以用于以下场景&#xff1a; 1、流程执行需要等待人工干…

安全狗入选2023年福建省数字经济核心产业领域创新企业名单

近日&#xff0c;福建省数字福建建设领导小组办公室公布了入选2023年全省数字经济核心产业领域创新企业名单。 作为国内云原生安全领导厂商&#xff0c;安全狗凭借综合表现与优势入选名单&#xff0c;荣膺“未来独角兽”称号。 据悉&#xff0c;此次对“未来独角兽”的评选条件…

Linux文件类型与属性

一、文件类型 Linux 系统下一共分为 7 种文件类型。通过 stat 命令或者 ls 命令来查看文件类型。 - &#xff1a;普通文件 d &#xff1a;目录文件 c &#xff1a;字符设备文件 b &#xff1a;块设备文件 l &#xff1a;符号链接文件 s &#xff1a;套接字文件 p &…

线性模型的介绍

一、背景 在一个理想的连续世界中&#xff0c;任何非线性的东西都可以被线性的东西来拟合&#xff0c;所以理论上线性模型可以模拟物理世界中的绝大多数现象。 线性模型&#xff08;Linear Model&#xff09;是机器学习中应用最广泛的模型&#xff0c;指通过样本特征的线性组…

【并发基础】一篇文章带你彻底搞懂Java线程中断的底层原理——interrupt()、interrupted()、isInterrupted()

目录 〇、Java线程中断与阻塞的区别 0.1 线程中断 0.2 线程阻塞 一、线程的中断 二、中断方法 2.1 void interrupt() 2.1.1 可中断的阻塞 2.1.2 不可中断的阻塞 2.1.3 实践案例 2.2 boolean isInterrupted() 2.3 boolean interrupted() 2.4 代码案例 三、源码分析…

指定GPU运行python程序

一、命令行运行python程序时 1、首先查看哪些GPU空闲&#xff0c;nvidia-smi显示当前GPU使用情况。 nvidia-smiGPU&#xff1a;编号&#xff0c;这里是0和1 Fan&#xff1a;风扇转速&#xff0c;在0到100%之间变动&#xff0c;第一个是29% Name&#xff1a;显卡名&#xff…

部署LVS-NAT群集实验

一、 实验准备 负载调度器&#xff1a;内网关 ens33&#xff1a;192.168.109.12&#xff0c;外网关 ens37&#xff1a;12.0.0.1外网 Web节点服务器1&#xff1a;192.168.109.13 Web节点服务器2&#xff1a;192.168.109.14 NFS服务器&#xff1a;192.168.109.11 客户端&#xf…

基于 Windows 安装 ESP32 Arduino 软件开发环境

ESP32 Arduino 源码库&#xff1a;arduino-esp32ESP32 Arduino 环境搭建说明&#xff1a;About Arduino ESP32 其他软件环境需求&#xff1a; Git 环境 1、安装 Arduino 软件 可在 Arduino 官网 获取 Windows 端 Arduino 安装包&#xff0c;如下&#xff1a; 使用如下 .exe 一…

中文编程最高境界,不用编程,会用excel就会用,香不香?

一直以来&#xff0c;关于中文编程的争议从未消停过。现如今&#xff0c;中文编程发展又是如何&#xff1f; ★为了实现中文编程&#xff0c;从未停下脚步 我们知道&#xff0c;中国人一直以来为了实现中文编程付出了不懈的努力&#xff0c;前前后后研发了几十种中文编程语言。…

ModuleNotFoundError: No module named ‘d2l’

目录 1. 下载李沐老师分享的源代码 step1&#xff1a;下载李沐老师分享的源代码&#xff1a; step3&#xff1a;Anaconda Prompt中安装d2l(这个l是英文) step4&#xff1a;运行代码&#xff0c;成功&#xff1a; &#xff08;番外&#xff09;ModuleNotFoundError: No mod…

R语言的Meta分析【全流程、不确定性分析】方法与Meta机器学习技术应用

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

Tensorflow GPU 版本安装教程

非常详细的 Tensorflow GPU 版本安装教程 一、安装Anaconda二、TensorFlow GPU 一、安装Anaconda 这一步比较简单&#xff0c;也没有太多的需要注意的&#xff0c;去官网下载即可&#xff1a; 官网地址如下&#xff1a; https://www.anaconda.com/blog/individual-edition-2…

今晚直播 | 思码逸陆春蕊:面对研发效能度量落地难点,如何让数据说话?

本期分享 本期 DevData Talks 邀请到了思码逸高级咨询专家陆春蕊老师。陆春蕊老师曾就职于 Oracle 美国&#xff0c;在软件质量、项目管理方面有着丰富的经验。在研发效能领域为上百家客户提供了技术、数据分析、实践落地等方面的咨询&#xff0c;协助客户提升研发效能10%-30%…

centos系统安装mysql8.0

centos系统安装mysql8.0 环境说明开始1、查看centos7中是否有MariaDB&#xff0c;MariaDB与MySQL关系请自行查阅2、如果有MariaDB&#xff0c;需要将 步骤1 中查询到的mairadb全部卸载&#xff0c;否则MySQL安装会出现问题3、查看本机是否已经安装过MySQL4、如果安装过MySQL&am…