動態

詳情 返回 返回

前端封裝一個流式音頻自動播放的類 - 動態 詳情

這個類會根據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()

user avatar aresn 頭像 jzcreative 頭像
點贊 2 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.