export function useAudioPlayer() { let audioContext = null; let audioQueue = []; let isPlaying = false; let currentTime = 0; // 当前播放时间(用于连续播放) const TARGET_SAMPLE_RATE = 16000; // 目标采样率(匹配后端) const CHANNELS = 1; // 单声道 const BIT_DEPTH = 16; // 16位深 const FADE_DURATION = 0.01; // 淡入淡出持续时间(秒) let actualSampleRate = TARGET_SAMPLE_RATE; // 实际使用的采样率 // 初始化AudioContext const initAudioContext = () => { if (!audioContext) { try { // 尝试创建指定采样率的AudioContext audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: TARGET_SAMPLE_RATE }); } catch (e) { // 如果浏览器不支持指定的采样率,使用默认采样率 console.warn(`浏览器不支持${TARGET_SAMPLE_RATE}Hz采样率,使用默认采样率:`, e); audioContext = new (window.AudioContext || window.webkitAudioContext)(); } actualSampleRate = audioContext.sampleRate; console.log(`使用的音频采样率: ${actualSampleRate}Hz`); currentTime = 0; // 重置播放时间 } }; // 添加淡入淡出效果,减少音频块过渡时的突变噪声 const applyFadeInOut = (audioBuffer) => { const channelData = audioBuffer.getChannelData(0); const fadeSamples = Math.floor(FADE_DURATION * audioBuffer.sampleRate); // 淡入效果 for (let i = 0; i < fadeSamples && i < channelData.length; i++) { channelData[i] *= (i / fadeSamples); } // 淡出效果 for (let i = channelData.length - fadeSamples; i < channelData.length; i++) { if (i >= 0) { channelData[i] *= ((channelData.length - i) / fadeSamples); } } }; // 创建低通滤波器,减少高频噪声 const createLowPassFilter = () => { const filter = audioContext.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 4000; // 4kHz截止频率 filter.Q.value = 0.707; // 巴特沃斯滤波器 filter.connect(audioContext.destination); return filter; }; // 播放音频块(支持流式PCM) const playAudioChunk = async (base64Audio) => { initAudioContext(); // 解码Base64音频数据 const audioBytes = Uint8Array.from(atob(base64Audio), c => c.charCodeAt(0)); audioQueue.push(audioBytes); if (!isPlaying) { processAudioQueue(); } }; // 处理音频队列(核心流式播放逻辑) const processAudioQueue = async () => { if (audioQueue.length === 0) { isPlaying = false; return; } isPlaying = true; const audioData = audioQueue.shift(); try { // 1. 处理首个WAV分片(带文件头) if (currentTime === 0) { // 解码完整WAV文件(仅首次) const audioBuffer = await audioContext.decodeAudioData(audioData.buffer); applyFadeInOut(audioBuffer); // 添加淡入淡出 playBuffer(audioBuffer); currentTime += audioBuffer.duration; // 更新播放时间 } // 2. 处理后续PCM分片(无文件头) else { // 确保数据是16位PCM格式 if (audioData.length % 2 !== 0) { // 如果数据长度不是偶数,可能存在问题,丢弃最后一个字节 audioData = audioData.slice(0, audioData.length - 1); } // 将16位PCM字节转换为Float32Array(AudioContext要求格式) const float32Data = convertPCMToFloat32(audioData); // 如果实际采样率与目标采样率不一致,进行重采样 const resampledData = actualSampleRate !== TARGET_SAMPLE_RATE ? resampleAudio(float32Data, TARGET_SAMPLE_RATE, actualSampleRate) : float32Data; // 创建音频缓冲区 const audioBuffer = audioContext.createBuffer(CHANNELS, resampledData.length, actualSampleRate); audioBuffer.copyToChannel(resampledData, 0); // 复制到音频通道 applyFadeInOut(audioBuffer); // 添加淡入淡出效果 playBuffer(audioBuffer); currentTime += audioBuffer.duration; // 更新播放时间 } } catch (error) { console.error("音频处理失败:", error); isPlaying = false; } }; // 重采样音频数据以匹配AudioContext的采样率 const resampleAudio = (inputData, inputSampleRate, outputSampleRate) => { const inputLength = inputData.length; const outputLength = Math.floor(inputLength * (outputSampleRate / inputSampleRate)); const outputData = new Float32Array(outputLength); for (let i = 0; i < outputLength; i++) { const inputIndex = i * (inputSampleRate / outputSampleRate); const index1 = Math.floor(inputIndex); const index2 = Math.min(index1 + 1, inputLength - 1); const fraction = inputIndex - index1; // 线性插值 outputData[i] = inputData[index1] * (1 - fraction) + inputData[index2] * fraction; } return outputData; }; // 播放音频缓冲区并调度下一个分片 const playBuffer = (audioBuffer) => { const source = audioContext.createBufferSource(); source.buffer = audioBuffer; // 添加低通滤波器减少噪声 const filter = createLowPassFilter(); source.connect(filter); // 确保从正确的时间点开始播放,处理可能的微小偏差 const scheduledTime = Math.max(currentTime, audioContext.currentTime); source.start(scheduledTime); // 播放结束后继续处理队列 source.onended = processAudioQueue; }; // 将16位PCM字节转换为Float32Array([-1.0, 1.0]范围) const convertPCMToFloat32 = (bytes) => { // 创建DataView确保正确处理小端字节序 const dataView = new DataView(bytes.buffer); const float32Array = new Float32Array(bytes.length / 2); // 16位PCM,每2字节一个样本 for (let i = 0; i < float32Array.length; i++) { // 使用getInt16方法并指定littleEndian=true来确保正确的字节序 const int16 = dataView.getInt16(i * 2, true); float32Array[i] = int16 / 32768; // 16位PCM最大值为32767,除以32768可得到[-1, 1)范围 } return float32Array; }; // 停止播放并清理 const stopPlayback = () => { if (audioContext) { // 不关闭AudioContext,只重置播放状态 currentTime = 0; // 重置播放时间 } audioQueue = []; isPlaying = false; }; return { playAudioChunk, stopPlayback }; }