這個類會根據websocket返回的base64音頻數據自動緩存播放,你可以修改緩衝音頻時長,可以播放和暫停
import { base64ToArrayBuffer } from '@/utils/AudioTools';
class AudioPlay {
// AudioContext
audioContext: AudioContext | any = null;
// AudioBufferSourceNode,該接口可以通過AudioBuffer對象來播放音頻數據
currentSource: AudioBufferSourceNode | null = null;
// 隊列
audioBufferQueue: any = [];
// 是否播放中
isPlaying: boolean = false;
// 緩衝n秒音頻
MIN_BUFFER_DURATION: number = 2;
// 累計緩衝時長
totalBufferedDuration: number = 0;
// 是否已傳入最後一段音頻數據
isFinish: boolean = false;
constructor() {
this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
this.audioBufferQueue = [];
this.isPlaying = false;
this.MIN_BUFFER_DURATION = 2;
this.totalBufferedDuration = 0;
this.isFinish = false;
}
/**
* 添加隊列數據和開始播放
* @param {base64} base64 base64音頻數據
* @param {boolean} isFinish 是否最後一次傳入
*/
async addQueuePlay(base64: string, isFinish: boolean) {
this.isFinish = isFinish;
// baset64轉ArrayBuffer
let arrayBuffer = base64ToArrayBuffer(base64);
// 異步解碼音頻文件中的ArrayBuffer
const buffer = await this.audioContext.decodeAudioData(arrayBuffer);
// 添加隊列
this.audioBufferQueue.push(buffer);
// 累加時長
this.totalBufferedDuration += buffer.duration;
// 沒有播放且緩衝時長大於等於n秒
if (!this.isPlaying && this.totalBufferedDuration >= this.MIN_BUFFER_DURATION) {
this.playNext();
} else if (!this.isPlaying && this.isFinish) {
// 沒有播放但音頻數據已完全傳入,兼容時長不足n秒的短音頻
this.playNext();
}
}
/**
* 播放下一個音頻
*/
async playNext() {
// 隊列長度為0, 暫停播放
if (this.audioBufferQueue.length === 0) {
return (this.isPlaying = false);
}
// 獲取隊列第一個音頻
let buffer = this.audioBufferQueue.shift();
// 創建新的AudioBufferSourceNode接口
const source = this.audioContext.createBufferSource();
// 音頻數據賦值
source.buffer = buffer;
// 賦值當前播放的音頻,用於後續的停止播放
this.currentSource = source;
// 為音頻源節點添加播放結束事件監聽器
// 噹噹前音頻播放結束時,會觸發此回調函數
source.onended = () => {
// 檢查是否已經接收完所有音頻數據,並且音頻隊列已空
if (this.isFinish && this.audioBufferQueue.length == 0) {
// 若條件滿足,説明所有音頻播放完畢,調用停止連接方法
this.stopConnection();
} else {
// 若仍有音頻在隊列中或者還未接收完所有數據,繼續播放下一個音頻
this.playNext();
}
};
// 將音頻源節點連接到音頻上下文的輸出設備,以便能聽到聲音
source.connect(this.audioContext.destination);
// 立即開始播放音頻
source.start(0);
// 從累計緩衝時長中減去當前播放音頻的時長
this.totalBufferedDuration -= buffer.duration;
// 將播放狀態標記為正在播放
this.isPlaying = true;
}
/**
* 停止音頻播放並重置相關狀態
*/
async stopConnection() {
try {
// 檢查當前是否有正在播放的音頻源節點
if (this.currentSource) {
// 立即停止當前音頻的播放
this.currentSource.stop(0);
// 斷開當前音頻源節點與音頻上下文輸出設備的連接
this.currentSource.disconnect();
// 清空當前音頻源節點的記錄
this.currentSource = null;
}
// 清空音頻緩衝隊列
this.audioBufferQueue.length = 0;
// 將播放狀態標記為停止
this.isPlaying = false;
// 重置最小緩衝時長為默認值
this.MIN_BUFFER_DURATION = 2;
// 重置累計緩衝時長為0
this.totalBufferedDuration = 0;
// 重置播放完畢標記為未完成
this.isFinish = false;
} catch (err) {
// 若手動斷開連接過程中出現錯誤,打印錯誤信息
console.log(`手動斷開失敗: ${err}`);
}
}
}
export default AudioPlay;
/**
* base64轉ArrayBuffer
* @param base64 base64字符串
* @returns ArrayBuffer
*/
export const base64ToArrayBuffer = (base64: any) => {
const binaryString = atob(base64);
const length = binaryString.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
};
頁面使用
<script setup lang="ts">
import AudioPlay from '@/utils/AudioPlay';
// 其它邏輯
// ...
// socket接收消息
let audioPlay = new AudioPlay();
const onMessage = async (data: any) => {
audioPlay.addQueuePlay(data.data.base64, data.data.isFinish);
};
// 語音-手動斷開連接
const stopConnection = async () => {
try {
audioPlay.stopConnection();
} catch (err) {
console.log(`手動斷開失敗: ${err}`);
}
};
</script>
參考文檔:
AudioContext()
AudioContext.decodeAudioData()
audioContext.createBufferSource()
AudioNode.connect()