// 定义PCMAudioPlayer配置类型 type PCMAudioPlayerConfig = { sampleRate?: number; channels?: number; bitDepth?: number; littleEndian?: boolean; loop?: boolean; bufferThreshold?: number; // 新增:缓冲阈值(秒) }; // 定义播放完成回调类型 type PlaybackCompleteCallback = () => void; // PCMAudioPlayer类用于播放PCM格式的音频数据(微信小程序版) export default class PCMAudioPlayer { // WebAudio上下文(优先使用) private audioContext: any = null; // 内部音频上下文(兼容低版本) private innerAudioContext: UniApp.InnerAudioContext | null = null; // 音频队列,存储待播放的音频数据 private audioQueue: ArrayBuffer[] = []; // 原始音频队列,用于重新播放 private originalQueue: ArrayBuffer[] = []; // 标记当前是否正在播放音频 private isPlaying = false; // 配置信息 private config: Required; // 表示音频是否处于暂停状态 private isPaused: boolean = false; // 播放完成回调函数 private onPlaybackComplete: PlaybackCompleteCallback | null = null; // WebAudio节点队列 private sourceNodes: any[] = []; // 下一个播放时间点 private nextPlayTime: number = 0; // 当前播放位置 private currentPosition: number = 0; // 总播放时长 private totalDuration: number = 0; // 是否使用WebAudio API private useWebAudio: boolean = false; // 缓冲阈值 private bufferThreshold: number = 0.3; // 300ms constructor(options: PCMAudioPlayerConfig = {}) { this.config = { sampleRate: options.sampleRate || 16000, channels: options.channels || 1, bitDepth: options.bitDepth || 16, littleEndian: options.littleEndian !== false, loop: options.loop || false, bufferThreshold: options.bufferThreshold || 0.3 }; // 初始化音频上下文 this.initAudioContext(); } /** * 初始化音频上下文 */ private initAudioContext(): void { try { // 尝试使用WebAudio API(基础库>=2.14.0) if (typeof wx.createWebAudioContext === 'function') { this.audioContext = wx.createWebAudioContext(); this.useWebAudio = true; console.log('使用WebAudio API'); } else { // 回退到InnerAudioContext this.innerAudioContext = uni.createInnerAudioContext(); this.innerAudioContext.onEnded(() => this.playNextChunk()); this.innerAudioContext.onError((res) => { console.error('音频播放错误:', res.errMsg); this.playNextChunk(); }); this.useWebAudio = false; console.log('使用InnerAudioContext'); } } catch (error) { console.error('初始化音频上下文失败:', error); throw error; } } /** * 设置播放完成回调 */ public setOnPlaybackComplete(callback: PlaybackCompleteCallback): void { this.onPlaybackComplete = callback; } /** * 输入Base64编码的音频数据 */ public async feedBase64(base64Data: string, autoPlay: boolean = false): Promise { try { const arrayBuffer = uni.base64ToArrayBuffer(base64Data); await this.feed(arrayBuffer, autoPlay); } catch (error) { console.error('处理Base64数据失败:', error); throw error; } } /** * 将PCM数据输入到音频队列中 */ public async feed(pcmData: ArrayBuffer, autoPlay: boolean = false): Promise { // 计算音频时长并更新总时长 const duration = pcmData.byteLength / (this.config.sampleRate * this.config.channels * (this.config.bitDepth / 8)); this.totalDuration += duration; this.audioQueue.push(pcmData); this.originalQueue.push(pcmData); if (autoPlay && !this.isPlaying && !this.isPaused) { await this.playNextChunk(); } } /** * 播放下一个音频数据块(WebAudio实现) */ private async playWebAudioChunk(): Promise { if (this.isPaused || this.audioQueue.length === 0) return; this.isPlaying = true; this.isPaused = false; const pcmData = this.audioQueue.shift()!; const audioBuffer = await this.decodePCMToAudioBuffer(pcmData); // 创建音频源节点 const source = this.audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(this.audioContext.destination); // 设置播放时间 if (this.nextPlayTime < this.audioContext.currentTime) { this.nextPlayTime = this.audioContext.currentTime; } source.start(this.nextPlayTime); this.nextPlayTime += audioBuffer.duration; // 更新播放位置 this.currentPosition += audioBuffer.duration; // 播放下一个片段 source.onended = () => { if (this.audioQueue.length > 0) { this.playWebAudioChunk(); } else { this.handlePlaybackComplete(); } }; this.sourceNodes.push(source); } /** * 解码PCM数据为AudioBuffer */ private async decodePCMToAudioBuffer(pcmData: ArrayBuffer): Promise { return new Promise((resolve, reject) => { // 将PCM转换为WAV格式 const wavData = this.pcmToWav(pcmData); this.audioContext.decodeAudioData( wavData, (buffer) => resolve(buffer), (err) => { console.error('解码音频失败:', err); reject(err); } ); }); } /** * 播放下一个音频数据块(InnerAudioContext实现) */ private async playInnerAudioChunk(): Promise { if (this.isPaused || this.audioQueue.length === 0) return; try { this.isPlaying = true; this.isPaused = false; const pcmData = this.audioQueue[0]; const wavData = this.pcmToWav(pcmData); // 创建临时文件播放 const tempFilePath = await this.arrayBufferToTempFilePath(wavData); if (!this.innerAudioContext) { throw new Error('音频上下文未初始化'); } this.innerAudioContext.src = tempFilePath; this.innerAudioContext.play(); } catch (error) { console.error('播放失败:', error); this.audioQueue.shift(); this.playNextChunk(); } } /** * 通用播放下一个音频块 */ private async playNextChunk(): Promise { if (this.useWebAudio) { return this.playWebAudioChunk(); } else { return this.playInnerAudioChunk(); } } /** * 将PCM数据转换为WAV格式 */ private pcmToWav(pcmData: ArrayBuffer): ArrayBuffer { const bytesPerSample = this.config.bitDepth / 8; const byteRate = this.config.sampleRate * this.config.channels * bytesPerSample; const blockAlign = this.config.channels * bytesPerSample; const dataSize = pcmData.byteLength; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); // 写入WAV头 this.writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); this.writeString(view, 8, 'WAVE'); this.writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); // 子块大小 view.setUint16(20, 1, true); // PCM格式 view.setUint16(22, this.config.channels, true); view.setUint32(24, this.config.sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, this.config.bitDepth, true); this.writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); // 写入PCM数据 const pcmView = new Uint8Array(pcmData); const wavView = new Uint8Array(buffer, 44); wavView.set(pcmView); return buffer; } /** * 将ArrayBuffer转换为临时文件路径 */ private arrayBufferToTempFilePath(arrayBuffer: ArrayBuffer): Promise { return new Promise((resolve, reject) => { const fileManager = uni.getFileSystemManager(); const tempFilePath = `${wx.env.USER_DATA_PATH}/tts_${Date.now()}.wav`; fileManager.writeFile({ filePath: tempFilePath, data: arrayBuffer, encoding: 'binary', success: () => resolve(tempFilePath), fail: (err) => reject(err) }); }); } /** * 暂停当前播放 */ public pause(): void { if (!this.isPlaying || this.isPaused) return; if (this.useWebAudio) { // WebAudio暂停需要特殊处理 this.audioContext.suspend().then(() => { this.isPaused = true; this.isPlaying = false; }); } else if (this.innerAudioContext) { this.innerAudioContext.pause(); this.isPaused = true; this.isPlaying = false; } } /** * 从暂停处继续播放 */ public async resume(): Promise { if (!this.isPaused) return; if (this.useWebAudio) { await this.audioContext.resume(); this.isPaused = false; this.isPlaying = true; // WebAudio需要重新调度 if (this.audioQueue.length > 0) { this.nextPlayTime = this.audioContext.currentTime; this.playWebAudioChunk(); } } else if (this.innerAudioContext) { this.innerAudioContext.play(); this.isPaused = false; this.isPlaying = true; } } /** * 停止播放并清空队列 */ public stop(): void { if (this.useWebAudio) { // 停止所有源节点 this.sourceNodes.forEach(source => { try { source.stop(); source.disconnect(); } catch (e) { console.warn('停止WebAudio节点失败', e); } }); this.sourceNodes = []; this.audioContext.close().catch(console.error); this.initAudioContext(); // 重新初始化 } else if (this.innerAudioContext) { this.innerAudioContext.stop(); } this.audioQueue = []; this.isPlaying = false; this.isPaused = false; this.nextPlayTime = 0; this.currentPosition = 0; } /** * 重新播放 */ public async replay(): Promise { this.stop(); this.audioQueue = [...this.originalQueue]; if (this.audioQueue.length > 0) { await this.playNextChunk(); } } /** * 设置循环播放 */ public setLoop(loop: boolean): void { this.config.loop = loop; if (this.innerAudioContext) { this.innerAudioContext.loop = loop; } // WebAudio在播放完成回调中处理循环 } /** * 处理播放完成 */ private handlePlaybackComplete(): void { this.isPlaying = false; this.isPaused = false; // 处理循环播放 if (this.config.loop && this.originalQueue.length > 0) { this.audioQueue = [...this.originalQueue]; this.playNextChunk(); return; } if (this.onPlaybackComplete) { try { this.onPlaybackComplete(); } catch (error) { console.error('播放完成回调执行失败:', error); } } } /** * 写入字符串到DataView */ private writeString(view: DataView, offset: number, str: string): void { for (let i = 0; i < str.length; i++) { view.setUint8(offset + i, str.charCodeAt(i)); } } /** * 获取当前状态 */ public getStatus() { return { isPlaying: this.isPlaying, isPaused: this.isPaused, queueLength: this.audioQueue.length, isLooping: this.config.loop, currentPosition: this.currentPosition, totalDuration: this.totalDuration, useWebAudio: this.useWebAudio }; } /** * 清空音频队列 */ public clearQueue(): void { this.audioQueue = []; } /** * 销毁播放器 */ public destroy(): void { this.stop(); if (this.audioContext) { this.audioContext.close().catch(console.error); } if (this.innerAudioContext) { this.innerAudioContext.destroy(); } } }