cms做企业网站6,温州品牌网站设计,广州优俊网站制作公司,网站站内优化方案写在前面
最近在写一个web项目#xff0c;需要实现web客户端之间的语音通话#xff0c;期望能够借助webSocket全双工通信的方式来实现#xff0c;但是网上没有发现可以正确使用的代码。网上能找到的一个代码使用之后只能听到“嘀嘀嘀”的杂音
解决方案#xff1a;使用Jso…写在前面
最近在写一个web项目需要实现web客户端之间的语音通话期望能够借助webSocket全双工通信的方式来实现但是网上没有发现可以正确使用的代码。网上能找到的一个代码使用之后只能听到“嘀嘀嘀”的杂音
解决方案使用Json来传递数据代替原有的二进制输入输出流
技术栈VUE3、SpingBoot、WebSocket
Java后端代码
pom.xml
配置Maven所需的jar包
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId
/dependencyWebSocketConfig.java
webSocket配置类
package com.shu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; Configuration
public class WebSocketConfig {/*** 注入ServerEndpointExporter* 这个bean会自动注册使用了ServerEndpoint注解声明的Websocket endpoint*/Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}WebSocketAudioServer.java
webSocket实现类其中roomId是语音聊天室的iduserId是发送语音的用户id
所以前端请求加入webSocket时候的请求样例应该是ws://localhost:8080/audio/1/123这个请求中1是roomId123是userId这里建议使用ws一般来说ws对于httpwss对应https
package com.shu.socket;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.PathParam;
import jakarta.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;
import java.util.concurrent.CopyOnWriteArraySet;/*** Author:Long**/
Component
Slf4j
ServerEndpoint(value /audio/{roomId}/{userId})
public class WebSocketAudioServer {/** 当前在线连接数。应该把它设计成线程安全的 */private static int onlineCount 0;/** 存放每个客户端对应的MyWebSocket对象。实现服务端与单一客户端通信的话其中Key可以为用户标识 */private static ConcurrentHashMapString, Session sessionPool new ConcurrentHashMapString, Session();private static CopyOnWriteArraySetWebSocketAudioServer webSocketSet new CopyOnWriteArraySet();/** 与某个客户端的连接会话需要通过它来给客户端发送数据 */private Session webSocketsession;/** 当前发消息的人员编号 */private String roomId;private String userId;/*** 连接建立成功调用的方法* * param param 发送者ID是由谁发送的* param WebSocketsession 可选的参数。session为与某个客户端的连接会话需要通过它来给客户端发送数据*/OnOpenpublic void onOpen(PathParam(value roomId) String roomId, PathParam(value userId) String userId,Session webSocketsession) {// 接收到发送消息的人员编号this.roomId roomId;this.userId userId;// 加入map中绑定当前用户和socketsessionPool.put(userId, webSocketsession);webSocketSet.add(this);this.webSocketsession webSocketsession;// 在线数加1addOnlineCount();System.out.println(user编号: userId 加入Room: roomId 语音聊天 总数为: webSocketSet.size());}/*** 连接关闭调用的方法*/OnClosepublic void onClose() {try {sessionPool.remove(this.userId);} catch (Exception e) {}}/*** 收到客户端语音消息后调用的方法**/OnMessage(maxMessageSize 5242880)public void onMessage(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();}}/*** 发生错误时调用** 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() {WebSocketAudioServer.onlineCount;}/*** 减少当前在线人数*/public static synchronized void subOnlineCount() {WebSocketAudioServer.onlineCount--;}public ListString getOnlineUser(String roomId) {ListString userList new ArrayListString();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数组数据实现了语音通话功能。
templatediv classplay-audiobutton clickstartCall refstart开始对讲/el-buttonbutton clickstopCall refstop结束对讲/el-button/div
/templatescript setup
// 语音聊天的变量
const audioSocket ref(null);
let mediaStack;
let audioCtx;
let scriptNode;
let source;
let play;
// 语音socket
const connectAudioWebSocket () {//获取tokenconst token window.sessionStorage.getItem(token);if (!token) {return;}let url ws://localhost:8080/audio/1/123; //roomId:1 ,userId123audioSocket.value new WebSocket(url); // 替换为实际的 WebSocket 地址audioSocket.value.onopen () {console.log(audioSocket connected);};audioSocket.value.onmessage (event) {// 将接收的数据转换成与传输过来的数据相同的Float32Arrayconst 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);// 也是由于只创建了一个音轨可以直接取到0const 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传输数据实际上传输的是Float32Arrayif (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即可