| 18 | } |
| 19 | |
| 20 | class MoshiProcessor extends AudioWorkletProcessor { |
| 21 | constructor() { |
| 22 | super(); |
| 23 | console.log("Audio Queue Processor initialized", currentFrame, sampleRate); |
| 24 | |
| 25 | // ============ 缓冲区配置 ============ |
| 26 | const frameSize = asSamples(40); // 每帧 80ms |
| 27 | |
| 28 | // 初始缓冲:收集多少数据后开始播放(越大越平滑,但延迟越高) |
| 29 | // 建议 240-320ms (3-4帧),可以容忍 2-3 个帧的网络抖动 |
| 30 | this.initialBufferSamples = 12 * frameSize; // 240ms |
| 31 | |
| 32 | // 启动后的额外等待时间 |
| 33 | this.startupDelaySamples = asSamples(40); // 40ms |
| 34 | |
| 35 | // Buffer underrun 后的最小恢复缓冲 |
| 36 | this.minRecoveryBuffer = asSamples(40); // 80ms |
| 37 | |
| 38 | // 状态初始化 |
| 39 | this.initState(); |
| 40 | |
| 41 | // 消息处理 |
| 42 | this.port.onmessage = (event) => { |
| 43 | if (event.data.type === "reset") { |
| 44 | console.log("[AudioProcessor] Reset state"); |
| 45 | this.initState(); |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | // 将音频帧加入队列 |
| 50 | const frame = event.data.frame; |
| 51 | this.audioQueue.push(frame); |
| 52 | this.totalQueuedSamples += frame.length; |
| 53 | |
| 54 | // 检查是否可以开始播放 |
| 55 | if (!this.isPlaying && this.totalQueuedSamples >= this.initialBufferSamples) { |
| 56 | this.startPlayback(); |
| 57 | } |
| 58 | |
| 59 | // 日志(前20个包) |
| 60 | if (this.packetCount < 20) { |
| 61 | console.log( |
| 62 | this.timestamp(), |
| 63 | `[Queue] Packet #${this.packetCount++}`, |
| 64 | `queued: ${asMs(this.totalQueuedSamples)}ms`, |
| 65 | `frame: ${asMs(frame.length)}ms` |
| 66 | ); |
| 67 | } |
| 68 | |
| 69 | // 发送状态给主线程 |
| 70 | this.port.postMessage({ |
| 71 | totalAudioPlayed: this.totalAudioPlayed, |
| 72 | actualAudioPlayed: this.actualAudioPlayed, |
| 73 | delay: event.data.micDuration - this.playbackPosition, |
| 74 | minDelay: this.minDelay, |
| 75 | maxDelay: this.maxDelay, |
| 76 | queueSize: this.totalQueuedSamples, |
| 77 | }); |
nothing calls this directly
no outgoing calls
no test coverage detected