WebSocket+Vue+SpringBoot實現(xiàn)語音通話的使用示例
整體思路
前端點擊開始對話按鈕后,將監(jiān)聽麥克風,獲取到當前的音頻,將其裝化為二進制數(shù)據(jù),通過websocket發(fā)送到webscoket服務端,服務端在接收后,將消息寫入給指定客戶端,客戶端拿到發(fā)送過來的二進制音頻后再轉(zhuǎn)化播放
注意事項
由于音頻轉(zhuǎn)化后的二進制數(shù)據(jù)較大,websocket默認的消息傳輸大小不能被接收,所以需要通過 @OnMessage(maxMessageSize=5242880)注解進行調(diào)整
Vue代碼
<template>
<div class="play-audio">
<el-button @click="startCall" ref="start">開始對講</el-button>
<el-button @click="stopCall" ref="stop">結(jié)束對講</el-button>
</div>
</template>
<script>
export default {
data() {
return {
ws: null,
mediaStack: null,
audioCtx: null,
scriptNode: null,
source: null,
play: true
}
},
methods: {
initWs1() {
//設置好友ID
let 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("接收到的數(shù)據(jù)--》"+ data)
// 將接收的數(shù)據(jù)轉(zhuǎn)換成與傳輸過來的數(shù)據(jù)相同的Float32Array
const buffer = new Float32Array(data)
// 創(chuàng)建一個空白的AudioBuffer對象,這里的4096跟發(fā)送方保持一致,48000是采樣率
const myArrayBuffer = this.audioCtx.createBuffer(1, 4096, 48000)
// 也是由于只創(chuàng)建了一個音軌,可以直接取到0
const nowBuffering = myArrayBuffer.getChannelData(0)
// 通過循環(huán),將接收過來的數(shù)據(jù)賦值給簡單音頻對象
for (let i = 0; i < 4096; i++) {
nowBuffering[i] = buffer[i]
}
// 使用AudioBufferSourceNode播放音頻
const source = this.audioCtx.createBufferSource()
source.buffer = myArrayBuffer
const gainNode = this.audioCtx.createGain()
source.connect(gainNode)
gainNode.connect(this.audioCtx.destination)
var muteValue = 1
if (!this.play) { // 是否靜音
muteValue = 0
}
gainNode.gain.setValueAtTime(muteValue, this.audioCtx.currentTime)
source.start()
}
this.ws.onerror = (e) => {
console.log('發(fā)生錯誤', e)
}
this.ws.onclose = () => {
console.log('socket closed')
}
},
// 開始對講
startCall() {
this.play = true
this.audioCtx = new AudioContext()
this.initWs1()
// 該變量存儲當前MediaStreamAudioSourceNode的引用
// 可以通過它關(guān)閉麥克風停止音頻傳輸
// 創(chuàng)建一個ScriptProcessorNode 用于接收當前麥克風的音頻
this.scriptNode = this.audioCtx.createScriptProcessor(4096, 1, 1)
navigator.mediaDevices
.getUserMedia({ audio: true, video: false })
.then((stream) => {
this.mediaStack = stream
this.source = this.audioCtx.createMediaStreamSource(stream)
this.source.connect(this.scriptNode)
this.scriptNode.connect(this.audioCtx.destination)
})
.catch(function (err) {
/* 處理error */
console.log('err', err)
})
// 當麥克風有聲音輸入時,會調(diào)用此事件
// 實際上麥克風始終處于打開狀態(tài)時,即使不說話,此事件也在一直調(diào)用
this.scriptNode.onaudioprocess = (audioProcessingEvent) => {
const inputBuffer = audioProcessingEvent.inputBuffer
// console.log("inputBuffer",inputBuffer);
// 由于只創(chuàng)建了一個音軌,這里只取第一個頻道的數(shù)據(jù)
const inputData = inputBuffer.getChannelData(0)
console.log("調(diào)用")
// 通過socket傳輸數(shù)據(jù),實際上傳輸?shù)氖荈loat32Array
if (this.ws.readyState === 1) {
// console.log("發(fā)送的數(shù)據(jù)",inputData);
this.ws.send(inputData)
}
}
},
// 關(guān)閉麥克風
stopCall() {
this.play = false
this.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 {
@Bean
public 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
* 消息收發(fā)
**/
@Controller
@ServerEndpoint(value = "/video/{senderID}/{recipientId}")
@Slf4j
public class WebSocketServer {
/** 當前在線連接數(shù)。應該把它設計成線程安全的 */
private static int onlineCount = 0;
/** 存放每個客戶端對應的MyWebSocket對象。實現(xiàn)服務端與單一客戶端通信的話,其中Key可以為用戶標識 */
private static ConcurrentHashMap<String, Session> webSocketSet = new ConcurrentHashMap<String, Session>();
/** 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù) */
private Session WebSocketsession;
/** 當前發(fā)消息的人員編號 */
private String senderID = "";
/**
* 連接建立成功調(diào)用的方法
* @param param 發(fā)送者ID,是由誰發(fā)送的
* @param WebSocketsession 可選的參數(shù)。session為與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
*/
@OnOpen
public void onOpen(@PathParam(value = "senderID") String param, @PathParam(value = "recipientId") String recipientId,Session WebSocketsession) {
System.out.println("人員-------**-------編號:"+param+":加入聊天");
System.out.println("盆友是:"+recipientId+"");
//接收到發(fā)送消息的人員編號
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中,綁定當前用戶和socket
webSocketSet.put(param, WebSocketsession);
//在線數(shù)加1
addOnlineCount();
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
if (StrUtil.isNotBlank(senderID)) {
//從set中刪除
webSocketSet.remove(senderID);
//在線數(shù)減1
subOnlineCount();
}
}
/**
* 收到客戶端消息后調(diào)用的方法
*
*設置最大接收消息大小
*/
@OnMessage(maxMessageSize=5242880)
public void onMessage(@PathParam(value = "senderID") String senderID ,@PathParam(value = "recipientId") String recipientId,InputStream inputStream) {
System.out.println(senderID+":發(fā)送給"+recipientId+"的消息-->"+inputStream);
try {
byte[] buff = new byte[inputStream.available()];
inputStream.read(buff, 0, inputStream.available());
Session session = webSocketSet.get("2");
synchronized (session) {
//給2號發(fā)送
session.getBasicRemote().sendBinary(ByteBuffer.wrap(buff));
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 發(fā)生錯誤時調(diào)用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
/**
* 為指定用戶發(fā)送消息
*
* @param message 消息內(nèi)容
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
//加同步鎖,解決多線程下發(fā)送消息異常關(guān)閉
synchronized (this.WebSocketsession){
this.WebSocketsession.getBasicRemote().sendText(message);
}
}
/**
* 獲取當前在線人數(shù)
* @return 返回當前在線人數(shù)
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 增加當前在線人數(shù)
*/
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
/**
* 減少當前在線人數(shù)
*/
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
測試方法
1.使用兩個瀏覽器模擬兩個用戶,首先在瀏覽器本地存儲一個用戶ID
用戶A–谷歌瀏覽器:

用戶B–火狐瀏覽器

2.點擊按鈕,進行測試
3.關(guān)于谷歌瀏覽器提示TypeError: Cannot read property ‘getUserMedia’ of undefined
原因:chrome下獲取瀏覽器錄音功能,因為安全性問題,需要在localhost或127.0.0.1或https下才能獲取權(quán)限
解決方案:
1.網(wǎng)頁使用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
在下方輸入欄內(nèi)輸入你訪問的地址url,然后將右側(cè)Disabled 改成 Enabled即可

c.然后瀏覽器會提示重啟, 點擊Relaunch即可

到此這篇關(guān)于WebSocket+Vue+SpringBoot實現(xiàn)語音通話的使用示例的文章就介紹到這了,更多相關(guān)WebSocket+Vue+SpringBoot語音通話內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Security 技術(shù)原理與實戰(zhàn)全解析
SpringSecurity是Spring生態(tài)的安全框架,提供認證、授權(quán)、攻擊防護等功能,基于過濾器鏈和上下文模型,支持可插拔架構(gòu)與聲明式安全控制,適用于分布式和云原生場景,是Java應用安全的核心解決方案,本文給大家介紹Spring Security 原理實戰(zhàn),感興趣的朋友一起看看吧2025-06-06
聊聊BeanUtils.copyProperties和clone()方法的區(qū)別
這篇文章主要介紹了聊聊BeanUtils.copyProperties和clone()方法的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Java異常鏈表throw結(jié)構(gòu)assert詳細解讀
這篇文章主要給大家介紹了關(guān)于Java中方法使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-08-08
springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter
這篇文章主要介紹了springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter,本文分步驟結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-06-06
Spring項目集成RabbitMQ及自動創(chuàng)建隊列
這篇文章主要介紹了Spring項目集成RabbitMQ及自動創(chuàng)建隊列,本文內(nèi)容分別在Spring(V5.2.6)和Spring Boot(V2.5.14)兩個項目中經(jīng)過了驗證,需要的朋友可以參考下2024-02-02
在Spring?MVC中使用@ControllerAdvice創(chuàng)建全局異常處理器的方法
在Spring?MVC中,可以使用@ControllerAdvice或@RestControllerAdvice注解來定義全局異常處理器類,并使用?@ExceptionHandler注解來定義處理特定異常的方法,本文就給大家介紹了Spring?MVC?@ControllerAdvice創(chuàng)建處理器的方法,需要的朋友可以參考下2023-08-08

