0


web网页端使用webSocket实现语音通话功能(SpringBoot+VUE)

写在前面

最近在写一个web项目,需要实现web客户端之间的语音通话,期望能够借助webSocket全双工通信的方式来实现,但是网上没有发现可以正确使用的代码。网上能找到的一个代码使用之后只能听到“嘀嘀嘀”的杂音

解决方案:

使用Json来传递数据代替原有的二进制输入输出流

技术栈:VUE3、SpingBoot、WebSocket

Java后端代码

pom.xml

配置Maven所需的jar包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
WebSocketConfig.java

webSocket配置类

packagecom.shu.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.socket.server.standard.ServerEndpointExporter;@ConfigurationpublicclassWebSocketConfig{/**
     *     注入ServerEndpointExporter,
     *     这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */@BeanpublicServerEndpointExporterserverEndpointExporter(){returnnewServerEndpointExporter();}}
WebSocketAudioServer.java

webSocket实现类,其中

roomId是语音聊天室的id

userId是发送语音的用户id

所以前端请求加入webSocket时候的请求样例应该是:

ws://localhost:8080/audio/1/123

这个请求中1是roomId,123是userId,这里建议使用ws,一般来说ws对于http,wss对应https

packagecom.shu.socket;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;importjakarta.websocket.OnClose;importjakarta.websocket.OnError;importjakarta.websocket.OnMessage;importjakarta.websocket.OnOpen;importjakarta.websocket.Session;importjakarta.websocket.server.PathParam;importjakarta.websocket.server.ServerEndpoint;importjava.io.BufferedInputStream;importjava.io.IOException;importjava.io.InputStream;importjava.nio.ByteBuffer;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.CopyOnWriteArraySet;/**
 * @Author:Long
 **/@Component@Slf4j@ServerEndpoint(value ="/audio/{roomId}/{userId}")publicclassWebSocketAudioServer{privatestaticConcurrentHashMap<String,Session> sessionPool =newConcurrentHashMap<String,Session>();privatestaticCopyOnWriteArraySet<WebSocketAudioServer> webSocketSet =newCopyOnWriteArraySet<>();privateSession webSocketsession;privateString roomId;privateString userId;@OnOpenpublicvoidonOpen(@PathParam(value ="roomId")String roomId,@PathParam(value ="userId")String userId,Session webSocketsession){// 接收到发送消息的人员编号this.roomId = roomId;this.userId = userId;// 加入map中,绑定当前用户和socket
        sessionPool.put(userId, webSocketsession);
        webSocketSet.add(this);this.webSocketsession = webSocketsession;// 在线数加1addOnlineCount();System.out.println("user编号:"+ userId +":加入Room:"+ roomId +"语音聊天  "+"总数为:"+ webSocketSet.size());}@OnClosepublicvoidonClose(){try{
            sessionPool.remove(this.userId);}catch(Exception e){}}@OnMessage(maxMessageSize =5242880)publicvoidonMessage(@PathParam(value ="roomId")String roomId,@PathParam(value ="userId")String userId,String inputStream){try{for(WebSocketAudioServer webSocket : webSocketSet){try{if(webSocket.webSocketsession.isOpen()&& webSocket.roomId.equals(roomId)&&!webSocket.userId.equals(userId)){
                        webSocket.webSocketsession.getBasicRemote().sendText(inputStream);}}catch(Exception e){
                    e.printStackTrace();}}}catch(Exception e){
            e.printStackTrace();}}@OnErrorpublicvoidonError(Session session,Throwable error){
        error.printStackTrace();}/**
     * 为指定用户发送消息
     *
     */publicvoidsendMessage(String message)throwsIOException{// 加同步锁,解决多线程下发送消息异常关闭synchronized(this.webSocketsession){this.webSocketsession.getBasicRemote().sendText(message);}}publicList<String>getOnlineUser(String roomId){List<String> userList =newArrayList<String>();for(WebSocketAudioServer webSocketAudioServer : webSocketSet){try{if(webSocketAudioServer.webSocketsession.isOpen()&& webSocketAudioServer.roomId.equals(roomId)){if(!userList.contains(webSocketAudioServer.userId)){
                        userList.add(webSocketAudioServer.userId);}}}catch(Exception e){
                e.printStackTrace();}}return userList;}}

VUE前端代码

audioChat.vue

这段代码是博主从自己的vue代码中截取出来的(原本的代码太多了),可能有些部分代码有函数没写上(如果有错的话麻烦大家在评论区指出,博主会及时修改

注意事项

之前有博客使用二进制数据输入输出流来向后端传输数据,但是功能无法实现,后来发现那位博主的数据并没有发成功,我直接

在Java中使用Json来传输float数组数据,实现了语音通话功能

<template>
  <div class="play-audio">
    <button @click="startCall" ref="start">开始对讲</el-button>
    <button @click="stopCall" ref="stop">结束对讲</el-button>
  </div>
</template>

<script setup>
// 语音聊天的变量
const audioSocket = ref(null);
let mediaStack;
let audioCtx;
let scriptNode;
let source;
let play;
// 语音socket
const connectAudioWebSocket = () => {
  let url = "ws://localhost:8080/audio/1/123"; //roomId:1 ,userId123
  audioSocket.value = new WebSocket(url); // 替换为实际的 WebSocket 地址

  audioSocket.value.onopen = () => {
    console.log("audioSocket connected");
  };

  audioSocket.value.onmessage = (event) => {
    // 将接收的数据转换成与传输过来的数据相同的Float32Array
    const jsonAudio = JSON.parse(event.data);

    // let buffer = new Float32Array(event.data);
    let buffer = new Float32Array(4096);
    for (let i = 0; i < 4096; i++) {
      // buffer.push(parseFloat(jsonAudio[i]));
      buffer[i] = parseFloat(jsonAudio[i]);
    }

    // 创建一个空白的AudioBuffer对象,这里的4096跟发送方保持一致,48000是采样率
    const myArrayBuffer = audioCtx.createBuffer(1, 4096, 16000);
    // 也是由于只创建了一个音轨,可以直接取到0
    const nowBuffering = myArrayBuffer.getChannelData(0);
    // 通过循环,将接收过来的数据赋值给简单音频对象
    for (let i = 0; i < 4096; i++) {
      nowBuffering[i] = buffer[i];
    }
    // 使用AudioBufferSourceNode播放音频
    const source = audioCtx.createBufferSource();
    source.buffer = myArrayBuffer;
    const gainNode = audioCtx.createGain();
    source.connect(gainNode);
    gainNode.connect(audioCtx.destination);
    var muteValue = 1;
    if (!play) {
      // 是否静音
      muteValue = 0;
    }
    gainNode.gain.setValueAtTime(muteValue, audioCtx.currentTime);
    source.start();
  };

  audioSocket.value.onclose = () => {
    console.log("audioSocket closed");
  };

  audioSocket.value.onerror = (error) => {
    console.error("audioSocket error:", error);
  };
};
// 开始对讲
function startCall() {
    isInChannel.value = true;
    play = true;
    audioCtx = new AudioContext();
    connectAudioWebSocket();

    // 该变量存储当前MediaStreamAudioSourceNode的引用
    // 可以通过它关闭麦克风停止音频传输

    // 创建一个ScriptProcessorNode 用于接收当前麦克风的音频
    scriptNode = audioCtx.createScriptProcessor(4096, 1, 1);
    navigator.mediaDevices
      .getUserMedia({ audio: true, video: false })
      .then((stream) => {
        mediaStack = stream;
        source = audioCtx.createMediaStreamSource(stream);

        source.connect(scriptNode);
        scriptNode.connect(audioCtx.destination);
      })
      .catch(function (err) {
        /* 处理error */
        isInChannel.value = false;
        console.log("err", err);
      });
    // 当麦克风有声音输入时,会调用此事件
    // 实际上麦克风始终处于打开状态时,即使不说话,此事件也在一直调用
    scriptNode.onaudioprocess = (audioProcessingEvent) => {
      const inputBuffer = audioProcessingEvent.inputBuffer;
      // console.log("inputBuffer",inputBuffer);
      // 由于只创建了一个音轨,这里只取第一个频道的数据
      const inputData = inputBuffer.getChannelData(0);
      // 通过socket传输数据,实际上传输的是Float32Array
      if (audioSocket.value.readyState === 1) {
        // console.log("发送的数据",inputData);
        // audioSocket.value.send(inputData);
        let jsonData = JSON.stringify(inputData);
        audioSocket.value.send(jsonData);

        // stopCall();
      }
    };
}
// 关闭麦克风
function stopCall() {
  isInChannel.value = false;
  play = false;
  mediaStack.getTracks()[0].stop();
  scriptNode.disconnect();
  if (audioSocket.value) {
    audioSocket.value.close();
    audioSocket.value = null;
  }
}
</script>

关于Chrome或Edge浏览器报错

关于谷歌浏览器提示

TypeError: Cannot read property ‘getUserMedia’ of undefined

解决方案:
1.网页使用https访问,服务端升级为https访问,配置ssl证书
2.使用localhost或127.0.0.1 进行访问
3.修改浏览器安全配置

在chrome浏览器中输入如下指令

chrome://flags/#unsafely-treat-insecure-origin-as-secure 

开启 Insecure origins treated as secure
在下方输入栏内输入你访问的地址url,然后将右侧Disabled 改成 Enabled即可

在这里插入图片描述

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


本文转载自: https://blog.csdn.net/Carmelo_7/article/details/135162517
版权归原作者 Carmelo_7 所有, 如有侵权,请联系我们删除。

“web网页端使用webSocket实现语音通话功能(SpringBoot+VUE)”的评论:

还没有评论