参考文章

  • 整体思路

前端点击开始对话按钮后,将监听麦克风,获取到当前的音频,将其装化为二进制数据,通过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即可

WebSocket+Vue+SpringBoot实现语音通话相关推荐

  1. B站云E办Vue+SpringBoot前后端分离项目——MVC三层架构搭建后台项目

    本项目来源B站云E办,笔记整理了项目搭建的过程和涉及的知识点.对于学习来说,不是复制粘贴代码即可,要知其然知其所以然.希望我的笔记能为大家提供思路,也欢迎各位伙伴的指正. 项目前端学习笔记目录 B站云 ...

  2. web端对接语音通话(腾讯云)

    实时音视频 实时语音通话(Web) - 场景实践 - 文档中心 - 腾讯云 按照要求注册腾讯云账号,跑通demo 1.集成TRTCCalling组件 // npm方式安装 npm install tr ...

  3. Springboot实现语音听写转文字功能

    Springboot实现语音听写转文字功能 背景概述 项目描述 技术特性 技术规格 输入 输出 解决思路 源码下载 背景概述 语音识别是一门交叉学科.近二十年来,语音识别技术取得显著进步,开始从实验室 ...

  4. vue 播放实时语音

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 vue 播放实时语音 前言 一.speechVoice.vue的封装 二.调用方式 前言 近期因项目需求,需要在网页端将文字实时转化为 ...

  5. vue页面实现语音播报

    vue页面实现语音播报 1.vue页面添加鼠标滑动事件 2.data中定义是否已经开启了语音播报 3.监听用户鼠标移入事件 4.voicePlay()方法,使用的是百度的tts 5. watch监听后 ...

  6. html5语音对讲,HTML5实时语音通话聊天,MP3压缩传输3KB每秒

    来源 | https://www.jianshu.com/p/017a158b6b18 自从Recorder H5 GitHub开源库优化后,对边录边转码成小语音片段文件实时上传服务器这种操作支持非常 ...

  7. php实时语音通话,HTML5实时语音通话聊天,MP3压缩传输3KB每秒

    一.把玩方法准备局域网内两台设备(Peer A.Peer B)用最新版本浏览器(demo未适配低版本)分别打开demo页面(也可以是同一浏览器打开两个标签) 勾选页面中的H5版语音通话聊天,在Peer ...

  8. Java项目:仿小米商城系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 本系统功能包括: 基于vue + Springboot前后端分离项目精简版仿小米商城 系统,注册登录,首页展示,商品展示,商品 ...

  9. Linux入门最终集! Vue SpringBoot Mybatis-Plus Linux 阿里云!

    Linux入门最终集! Vue & SpringBoot & Mybatis-Plus & Linux & 阿里云! 一.Xshell连接时出现警告 The remot ...

最新文章

  1. css3 3d变换和动画——回顾
  2. ArcGIS JavaScript API 实现基本的地图功能
  3. Intel Realsense D435 composite_frame类 frame类 video_frame类 depth_frame类的关系
  4. 利用ASP.NET一般处理程序动态生成Web图像(转)
  5. jspservlet初体验——用户登录功能实现
  6. HDU4454(几何+三分)
  7. [原创]ActionScript3游戏中的图像编程(连载五)
  8. 《HTML5+CSS3网页设计入门必读》——2.8 转变(CH-CH变化)
  9. 基础:Linux 核心源码是怎么组织的?-转
  10. eureka server配置_Spring Boot(十):注册中心Eureka-客户端视角
  11. android背景图边框渐变,GitHub - jvyun/TestShape: 使用自定义属性替代项目中的shape文件,可以给View设置背景色、弧度、背景渐变、边框、边框颜色、渐变方向等...
  12. Java排序算法(四):Shell排序
  13. UniWebView3 使用中遇到的坑
  14. android 改机型玩王者,抱歉,Xposed真的可以为所欲为——2.改为OV机型流畅玩耍高帧率王者农药...
  15. 可與言而不與之言,失人。不可與言而與之言,失言。知者不失人,亦不失言。
  16. 2018Java阿里编程测验_2018阿里编程测验题:最短汉密尔顿回路
  17. c语言对随机数进行快速排序,C语言快速排序与二分查找算法示例
  18. trs ids身份服务器系统实现统一身份认证与单点登录平台技术,统一身份认证系统设计与实现...
  19. SQL1 插入记录(一)
  20. AO采集用友oracle,AO2011系统如何采集用友GRP-R9导出的ASD文件

热门文章

  1. systemctl status mysqld.service或者systemctl start mysqld 启动失败的解决办法
  2. 迈普路由器BFD与ospf联动基本功能
  3. Excel插件--E达通工具箱(VB.net)
  4. 2022re:Invent:亚马逊云科技拥有强大的云原生数据能力
  5. 集社预付费系统微信公众号介绍(用户入口)
  6. 写给小白的常见三种加密方式:MD5,对称非对称加密
  7. 软件定义网络技术现状分析
  8. kali安装Aria2
  9. 【Pyside6】桌面应用--目录、代码结构设计(附案例)
  10. clamav 使用clamdscan检测病毒