Files
2026-07-13 13:30:30 +08:00

93 lines
3.0 KiB
JavaScript

/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* An audio worklet processor that stores the PCM audio data sent from the main thread
* to a buffer and plays it.
*/
class PCMPlayerProcessor extends AudioWorkletProcessor {
constructor() {
super();
// Init buffer
this.bufferSize = 24000 * 180; // 24kHz x 180 seconds
this.buffer = new Float32Array(this.bufferSize);
this.writeIndex = 0;
this.readIndex = 0;
// Handle incoming messages from main thread
this.port.onmessage = (event) => {
// Reset the buffer when 'endOfAudio' message received
if (event.data.command === 'endOfAudio') {
this.readIndex = this.writeIndex; // Clear the buffer
console.log("endOfAudio received, clearing the buffer.");
return;
}
// Decode the base64 data to int16 array.
const int16Samples = new Int16Array(event.data);
// Add the audio data to the buffer
this._enqueue(int16Samples);
};
}
// Push incoming Int16 data into our ring buffer.
_enqueue(int16Samples) {
for (let i = 0; i < int16Samples.length; i++) {
// Convert 16-bit integer to float in [-1, 1]
const floatVal = int16Samples[i] / 32768;
// Store in ring buffer for left channel only (mono)
this.buffer[this.writeIndex] = floatVal;
this.writeIndex = (this.writeIndex + 1) % this.bufferSize;
// Overflow handling (overwrite oldest samples)
if (this.writeIndex === this.readIndex) {
this.readIndex = (this.readIndex + 1) % this.bufferSize;
}
}
}
// The system calls `process()` ~128 samples at a time (depending on the browser).
// We fill the output buffers from our ring buffer.
process(inputs, outputs, parameters) {
// Write a frame to the output
const output = outputs[0];
const framesPerBlock = output[0].length;
for (let frame = 0; frame < framesPerBlock; frame++) {
// Write the sample(s) into the output buffer
output[0][frame] = this.buffer[this.readIndex]; // left channel
if (output.length > 1) {
output[1][frame] = this.buffer[this.readIndex]; // right channel
}
// Move the read index forward unless underflowing
if (this.readIndex != this.writeIndex) {
this.readIndex = (this.readIndex + 1) % this.bufferSize;
}
}
// Returning true tells the system to keep the processor alive
return true;
}
}
registerProcessor('pcm-player-processor', PCMPlayerProcessor);